Step1: Open this .RMD file in R Studio and click the “Preview” button above. A formatted version should pop up in a browser window. IF not, troubleshoot that!
manual outline
- Housekeeping
- SET WORKING DIRECTORY
- this R Markdown (.RMD) document was created using R Studio
- plain text
- “Reproducible Research” Movement
- R from https://www.r-project.org/
- R Studio from https://www.rstudio.com/products/rstudio/download/
- Environment tab (Rstudio)
- Files/Plots/Packages/Help/Viewer tab (Rstudio)
- Preview/view as HTML (R Markdown in Rstudio)
- Outline tab (R Markdown in Rstudio)
- Intro
- Statement of Problem
- Overview of Experimental Data
- Measuring the Freshening Power of the Hop (relative to non-dryhopped controls)
- calculated metrics
- \(\Delta\)ABV
- \(\Delta\)ABW
- \(\Delta{calculated}\)CO\(_2\)
- \(\Delta\) °plato
- \(\Delta\)SG\(^{20/20}\)
- fold-increase ethanol from hop addition (in \(\frac{pounds}{bbl}\) and wt% or \(\frac{grams}{100 mL}\))
- fold-increase CO\(_2\) from hop addition (in g/L and CO\(_2\) volumes)
- Load Libraries
- Data Import, Tidy and Transform
- Data Visualization
- scatter plots
- scatter plots with added dimensions
- Modeling overview
- linear regression
- residuals
- Modeling FPH
intro
ascii font/art from http://www.network-science.de/ascii/
# __ .__ .___
# _/ |_|__| __| _/__.__.
# \ __\ |/ __ < | |
# | | | / /_/ |\___ |
# |__| |__\____ |/ ____|
# \/\/
# \/
# ||
# ||
# ||
# _ /\
# | |_ _ _ __ _ _ _ ___/ _|___ _ _ _ __
# | _| '_/ _` | ' \(_-< _/ _ \ '_| ' \
# \__|_| \__,_|_||_/__/_| \___/_| |_|_|_|
# // \\
# // \\
# // \\
# // \\
# _ // _ _ \\ _ _
# __ _(_)____ _ __ _| (_)______ _ __ ___ __| |___| |
# \ V / (_-< || / _` | | |_ / -_)======| ' \/ _ \/ _` / -_) |
# \_/|_/__/\_,_\__,_|_|_/__\___| |_|_|_\___/\__,_\___|_|
This is primarily an excercise in data wrangling (tidy+transform) and markdown formatting using R Studio. In that context, this analysis is a preliminary exploration of potential metrics for Hop Freshening Power (synonyms: ‘dryhop creep’, ‘ABV creep’, ‘dry-hop creep’) and to model this aspect of the warm-dryhopping process.
Statement of Problem
The warm-dryhopping process can profoundly impact the body, alcohol and vicinal diketone content of the beer. A common metric for this phenomenon has not been established.
Overview of Experimental Data
Through experience in brewing we know that endpoints of dryhopping include:
- flavor impact (always)
- impact on visual/presentation (sometimes)
- ethanol increase accompanied by decrease in specific gravity (sometimes)
- CO2 increase (sometimes)
- diacetyl/VDK increase (sometimes)
- and so on…
Experiments were carried out where
- multiple replicate 250-mL samples of [nearly] end-fermented beer were collected from fermenters into 12oz amber bottles
- the samples were randomized into groups of three, then each group was either
- dry-hopped at ~1.0 pounds/bbl (treatment), or
- not dry-hopped (control)
- all were hand-crimped with foil, stored for various times (mostly at room temp, a few in the cold), then tested on an Anton Paar DMA4500/Alcolyzer classic
Focusing on ethanol increase as the endpoint, we know these to be relevant factors:
- variety of hops (five different varieties)
- presence of live yeast (true in all cases for these data)
- temp.C = temperature during dryhopping (mostly warm, with a few in the cold)
- amount of contact time between hops and beer (up to 7 weeks)
For more details on this dataset see the manuscript.
please send complaints and corrections to luke.chadwick@gmail.com! * Data are from the file “ujbc_a_1469081_sm5496.txt” (supplementary data from Jacob A. Kirkendall, Carter A. Mitchell & Lucas R. Chadwick (2018): The Freshening Power of Centennial Hops, Journal of the American Society of Brewing Chemists Volume 76, Issue 3, Pages 178-184 (2018) DOI: 10.1080/03610470.2018.1469081* available from https://www.tandfonline.com/doi/full/10.1080/03610470.2018.1469081?scroll=top&needAccess=true
load libraries
flash forward! scroll to the bottom and run bigchunk_tidytransform
import data
rm(list = ls()) # clear workspace
mydata <-read.csv("ujbc_a_1469081_sm5496.txt",stringsAsFactors = FALSE) ## SPECIFY filename
mydata %>% filter(expt ==" 2A") %>% dplyr::select(sample_id)
`as_dictionary()` is soft-deprecated as of rlang 0.3.0.
Please use `as_data_pronoun()` instead
[90mThis warning is displayed once per session.[39m`new_overscope()` is soft-deprecated as of rlang 0.2.0.
Please use `new_data_mask()` instead
[90mThis warning is displayed once per session.[39mThe `parent` argument of `new_data_mask()` is deprecated.
The parent of the data mask is determined from either:
* The `env` argument of `eval_tidy()`
* Quosure environments when applicable
[90mThis warning is displayed once per session.[39m`overscope_clean()` is soft-deprecated as of rlang 0.2.0.
[90mThis warning is displayed once per session.[39m
#dput(names(mydata))
manual add time zero to “days on hops” experiment!
Anton Paar data at time zero for kinetics experiment (triplicate samples run immediately after sample collection, while the remainder were being dry-hopped): Date time sample no. ABV ABW OE (P) Er Ea SG 20/20 RDF ADF sample_id 07/27/17 3:26 PM 03_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-41 07/27/17 3:30 PM 04_ 6.68 5.2 15.95 6.08 3.73 1.01461 63.89 76.6 JK1-20-50 07/27/17 3:34 PM 05_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-87
## fill in MISSING DATA :( add time zero data for time series analysis)
## nested ifelse to add time zero plato values for each expt:
mydata<- mydata %>% mutate(., initial_plato = ifelse(expt==" 2A", 3.74, ifelse(expt==" 1A", 3.69, ifelse(expt==" 1B", 3.53, ifelse(expt==" 3B", 3.54, ifelse(expt==" 4B", 4.66, NA))))))
## add complete time-zero anton paar data for time series (expt2A)
## quadruple-up each triplicate time-zero measurement (one copy for each NH/DH & rouse/still combo)
## note: this practice is of questionable statistical rigor. In retrospect it would have been better (and a lot easier) to just run 12 samples through Anton on day0!
timezero <- mydata %>% filter(expt==" 2A") %>% arrange(special_group) %>% slice(1:12)
#data.entry(timezero)
timezero$Test_Date <- rep(c("7/27/2017","7/27/2017","7/27/2017"),4)
timezero$Test.time <- rep(c("3:26 PM", "3:30 PM", "3:34 PM"),4)
timezero$ABV <- 6.68
timezero$ABW <- 5.2
timezero$OE <- 15.95
timezero$Er <- 6.08
timezero$Ea <- rep(c(3.74, 3.73, 3.74),4)
timezero$SG <- rep(c(1.01462, 1.01461, 1.01462),4)
timezero$RDF <- rep(c(63.88, 63.89, 63.88),4)
timezero$ADF <- rep(c(76.58, 76.60, 76.58),4)
timezero$Calories <- rep(c(215.37,215.34,215.35),4)
timezero[timezero$hop=="DH",]$contact_days <-0
timezero[,c(28:31)] <- 0
mydata <- rbind(timezero,mydata)
write.csv(mydata, "quickie.csv", row.names = FALSE)
mydata<-read.csv("quickie.csv", stringsAsFactors = FALSE)
### this is a routine for inspecting data ###
mydatatypes<- as.data.frame(cbind(sapply(mydata, class))) ## table of datatypes
mydatatypes$headers <- rownames(mydatatypes) ## convert rownames to values
na_count <-as.data.frame(sapply(mydata, function(y) sum(length(which(is.na(y)))))) ## count NAs by column
mydataOverview<-as.data.frame(cbind(na_count,mydatatypes)) ## table of NA counts and datatypes
names(mydataOverview) <- c("NA_count","Data_Class", "Header")
sum(is.na(mydata)) ## total count of NA values in entire sheet (231 in this case)
[1] 261
mydataOverview %>% filter(NA_count>0) ## breakdown of NA values
tidy & transform
note: this is a base R +dplyr data wrangling exercise! in general it’s best to use lubridate package for date/timestamps!
- create date column (one at a time)
- here we’re creating new columns in POSIXct format, based on the particular date format used in the source data (ujbc_a_1469081_sm5496.txt; see ?as.POSIXct):
test_df<- mydata # (original dataframe to be used for testing/illustration)
x_brew_date.POSIXct<- as.POSIXct(mydata$brew_date, format = "%m/%d/%Y") # as a standalone vector (USELESS here), or:
test_df$brew_date.POSIXct <-as.POSIXct(mydata$brew_date, format = "%m/%d/%Y") # as a "new column" in our dataframe
- ADVANCED data wrangling:
- create multiple date columns using FORLOOP (here we take advantage of the pattern that of our (character) date/time columns have the string “date” in the headername. First make character vector of all column names containing string “date”, then run FORLOOP over each date column). This forloop will convert datecols into POSIXct format (R/universal datetime format). They say it’s best to avoid forloops in general, and in this case you would probably use functions from lubridate package - and in general be aware there’s probably a specific tool for the task at hand)!!
datecols<- dput(names(dplyr::select(mydata, matches("date")))) ## headers containing string "date" => c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date")
c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date"
)
## FORLOOP
for (icol in datecols) {
newcol = paste0(icol,".POSIXct")
# print(newcol)
mydata[, newcol] = as.POSIXct(mydata[, icol],format = "%m/%d/%Y") ### CREATE NEW columns with POSIXct
# mydata[, newcol] = as.POSIXct(as.numeric(mydata[, icol]) * (60*60*24), origin="1899-12-30") ### microsoft times
}
- create dayofaddition, daysonhops, hops_g_100mL, pounds_bbl variables with dplyr “mutate”
mydata<- mydata %>%
mutate(dayofaddition = as.integer(as.numeric(difftime(dryhop_date.POSIXct,brew_date.POSIXct))),
daysonhops = as.integer(as.numeric(difftime(Test_Date.POSIXct,dryhop_date.POSIXct))/(60*60*24)),
hops_g_100mL = (mg_hops/volume_mL)/10,
pounds_bbl = (mg_hops/volume_mL)*117/454
)
- ADVANCED data wrangling: manually unpack overloaded columns with grepl
mydata$OX<-grepl("OX", mydata$Hop_type) ## create logical "OX" column
mydata$rouse<-grepl("rouse", mydata$special_group)## create logical column
mydata$Grind<-grepl("Grind", mydata$Hop_type) ## create logical column
mydata$Cone<-grepl("Cone", mydata$Hop_type) ## create logical column
mydata$harvest2014<-grepl("14", mydata$Hop_type) ## create logical column
mydata$harvest2015<-grepl("15", mydata$Hop_type) ## create logical column
mydata$harvest2017<-grepl("17", mydata$Hop_type) ## create logical column
- ADVANCED data wrangling: ifelse statements for harvestyear, form_of_hops, and DH_temp
## ifelse statement for harvestyear (if neither 2014 nor 2015 nor 2017, then 2016)
mydata$harvestYear <- ifelse(
mydata$harvest2014==TRUE, 2014,
ifelse(mydata$harvest2015==TRUE, 2015,
ifelse(mydata$harvest2017==TRUE, 2017, 2016)))
dput(levels(as.factor(mydata$harvestYear)))
c("2014", "2015", "2016", "2017")
## ifelse statement for form of hops (if neither cone nor ground nor NH, then pellet)
mydata$form_of_hops <- ifelse(
mydata$Cone==TRUE, "cone",
ifelse(mydata$Grind==TRUE, "ground",
ifelse(mydata$Hop_type=="NH", "NH", "pellet")))
dput(levels(as.factor(mydata$form_of_hops)))
c("cone", "ground", "NH", "pellet")
## ifelse statement for temperature greater or less than 10
mydata$DH_temp <- ifelse(
mydata$temp.C<10, "cold",
ifelse(mydata$temp.C>10, "warm", "something else"))
dput(levels(as.factor(mydata$DH_temp)))
c("cold", "warm")
- ADVANCED data wrangling: create “variety” column starting with overloaded “Hop_type” column, then stripping away all the non-variety information
mydata$variety<-mydata$Hop_type
mydata$variety<- gsub("[0-9]+","", mydata$variety) ## remove all numbers
mydata$variety<- gsub("OX","", mydata$variety) ## remove specific text
mydata$variety<- gsub("Grind","", mydata$variety)
mydata$variety<- gsub("Cone","", mydata$variety)
mydata$variety<- gsub(" ","", mydata$variety) ## remove spaces
dput(levels(as.factor(mydata$variety)))
c("AMAR", "CASC", "CENT", "CIT", "NH", "SIM")
- ADVANCED data wrangling: create “EXPTnew” (each unique experimental group designated in a single variable)
mydata<- mydata %>% mutate(EXPTnew=paste0("group",expt, substr(special_group, 1,2),as.character(rouse)))
# clean it up by removing "NA" and any spaces due to canarycode bug
mydata$EXPTnew <- gsub(" ","", mydata$EXPTnew) ## remove any spaces
mydata$EXPTnew <- gsub("NA","", mydata$EXPTnew) ## remove "NA"
dput(levels(as.factor(mydata$expt)))
c(" 1A", " 1B", " 2A", " 3A", " 3B", " 4B")
dput(levels(as.factor(mydata$EXPTnew)))
c("group1AFALSE", "group1BFALSE", "group2A01FALSE", "group2A01TRUE",
"group2A05FALSE", "group2A05TRUE", "group2A12FALSE", "group2A12TRUE",
"group2A19FALSE", "group2A19TRUE", "group2A25FALSE", "group2A25TRUE",
"group2A33FALSE", "group2A33TRUE", "group2A42FALSE", "group2A42TRUE",
"group3AFALSE", "group3BFALSE", "group4BFALSE")
select and rearrange columns
(experimental factors on the left, then measurements, followed by calculations and then all date columns on the right):
mydata <- mydata %>%
dplyr::select(sample_id,expt, EXPTnew, hop, BINhop, variety, OX, harvestYear, form_of_hops,special_conditions, rouse, daysonhops, dayofaddition, DH_temp, temp.C, hops_g_100mL, pounds_bbl,
ABV, ABW, OE, Er, Ea, SG, RDF, ADF, Calories,
dhop_day, contact_days, REF_NH, ABV_increase,
brew_date.POSIXct, sample_collection_date.POSIXct, dryhop_date.POSIXct,Test_Date.POSIXct, initial_plato)
## remove ".POSIXct" suffix. Leaving it as-is will only add to confusion if/when these data are saved and re-imported (and become 'character' format!)
colnames(mydata) = gsub(".POSIXct", "", colnames(mydata))
compute mean NH (control) values
(NH = not dry-hopped)
## mean NH (control) values for each EXPTnew group
mean.control_NH<- mydata %>%
group_by(EXPTnew) %>%
filter(hop=="NH") %>%
summarise_at(vars(ABV, ABW, Ea, SG),funs(mean, n()))
## replace "_mean" with ".control_NH" in column names
colnames(mean.control_NH) = gsub("_mean", ".control_NH", colnames(mean.control_NH))
compute \(\Delta\) values (changes relative to un-dryhopped controls) using objects created above…
- difference of each ABV,ABW,Ea, and SG data point from corresponding mean.control_NH values (unhopped samples in same EXPTnew group)
## first join our data with mean ABV for unhopped samples in given experiment (mean.control_NH; calculated above)
FPHcalc<- left_join(mydata, mean.control_NH, by="EXPTnew")
## calculate ABW_increase by subtracting each individual ABW measurement from respective mean.control_NH:
FPHcalc$delta.ABV <- FPHcalc$ABV - FPHcalc$ABV.control_NH
FPHcalc$delta.ABW <- FPHcalc$ABW - FPHcalc$ABW.control_NH
FPHcalc$delta.plato <- FPHcalc$Ea - FPHcalc$Ea.control_NH
FPHcalc$delta.SG <- FPHcalc$SG - FPHcalc$SG.control_NH
## the control samples have served their purpose, now remove them from dataset. The following calculations are only meaningful for dry-hopped samples.
FPHcalc<- FPHcalc %>% filter(hop=="DH")
calculate corresponding CO2 production
- following Bamforth (describing Balling equation) “…more realistically, the ethanol yield is more like 0.46 g and carbon dioxide 0.44 g from 1 g sugar” (p. 137 in Brewing Materials and Processes: A Practical Approach to Beer Excellence, Edited by Charles Bamforth Academic Press, 2016)
FPHcalc$calcCO2_increase <- FPHcalc$delta.ABW*(0.44/0.46)
##convert calcCO2_increase (in g/100mL) to calculated CO2 volumes added
## g/L = 10* g/100mL
## The conversion factor from volumes of CO2 to CO2 by weight (g/L) is 1.96. For example: 2.5 volumes x 1.96 = 4.9 g/l.
FPHcalc$calcCO2vols_increase <- FPHcalc$calcCO2_increase*10/1.96
define “FPH” as amount produced per amount dry-hops added (all in g/100mL):
## FPH = Fold Production due to Hops (fold-increase by mass: amount of given endpoint relative to amount of hops added)
##
FPHcalc$FPH_EtOH = FPHcalc$delta.ABW/FPHcalc$hops_g_100mL
FPHcalc$FPH_CO2 = FPHcalc$calcCO2_increase/FPHcalc$hops_g_100mL
FPHcalc$FPH_plato = FPHcalc$delta.plato/FPHcalc$hops_g_100mL
## replacing time-zero time values with a very small number (rather than exactly zero) will prevent issues with analysis of nonlinear models
FPHcalc[FPHcalc$daysonhops==0,]$daysonhops <- 0.01
## and save the transformed data to csv:
write.csv(FPHcalc,"FPHcalc.csv", row.names = FALSE)
FPHcalc <- read.csv("FPHcalc.csv", stringsAsFactors = TRUE)
df<- FPHcalc %>% group_by(EXPTnew) %>%
summarise_at(vars(hops_g_100mL,pounds_bbl,delta.ABV, delta.ABW, delta.plato, FPH_plato, FPH_EtOH, FPH_CO2, calcCO2vols_increase),funs(round(mean(.), 2))) %>%
arrange(desc(FPH_EtOH))
df
intermission?
visualize
vector~vector*vector plots
df <-FPHcalc
p1<- ggplot(df, aes(y=FPH_EtOH,x=OE, color=contact_days)) + geom_point(size=2)
p2<- ggplot(df, aes(y=FPH_EtOH,x=ADF, color=contact_days)) + geom_point(size=2)
p3<- ggplot(df, aes(y=FPH_EtOH,x=ABW, color=contact_days)) + geom_point(size=2)
p4<- ggplot(df, aes(y=FPH_EtOH,x=Ea, color=contact_days)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

x~y*(4 vectors) by color
df <- FPHcalc
x<- df$Ea
y<- df$ABW #FPH_EtOH
p1<- ggplot(df, aes(x,y, color=form_of_hops)) + geom_point(size=2)
p2<- ggplot(df, aes(x,y, color=brew_date)) + geom_point(size=2)
p3<- ggplot(df, aes(x,y, color=rouse)) + geom_point(size=2)
p4<- ggplot(df, aes(x,y, color=pounds_bbl)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

3D plots with library(plotly)
# interactive 3D plots with library(plotly)
plot_ly(data = df, z = df$PLATO.hopdrop, x = ~df$daysonhops, y = df$ABW)
No trace type specified:
Based on info supplied, a 'scatter3d' trace seems appropriate.
Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
No trace type specified:
Based on info supplied, a 'scatter3d' trace seems appropriate.
Read more about this trace type -> https://plot.ly/r/reference/#scatter3d
No scatter3d mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
Amunategui “Using Correlations To Understand Your Data”
mydata<-FPHcalc %>% dplyr::select(expt,variety,form_of_hops,pounds_bbl,special_conditions,rouse,daysonhops,DH_temp,brew_date,ABW,SG,OE,ADF,RDF,calcCO2vols_increase,FPH_CO2, FPH_EtOH) ## last one is 100% in j
## note use of "dplyr::select" because one of these packages is conflicting with dplyr commands :()
## following Manuel Amunategui https://www.youtube.com/watch?v=igPQ-pI8Bjo
## Using Correlations To Understand Your Data: Machine Learning With R
##functions for flattenSquareMatrix
cor.prob <- function (X, dfr=nrow(X) -2) {
R<- cor(X, use="pairwise.complete.obs")
above<- row(R) < col(R)
r2 <- R[above]^2
Fstat<- r2 * dfr/(1-r2)
R[above] <- 1- pf(Fstat, 1, dfr)
R[row(R) == col(R)] <- NA
R
}
flattenSquareMatrix <- function(m) {
if( (class(m) != "matrix") | (nrow(m)!=ncol(m))) stop("Must be a square matrix.")
if(!identical(rownames(m), colnames(m))) stop("Row and column names must be equal.")
ut <- upper.tri(m)
data.frame(i = rownames(m)[row(m)[ut]],
j = rownames(m)[col(m)[ut]],
cor=t(m)[ut],
p=m[ut])
}
## library(caret) to dummify everything (turn all characters&factors into columns; ignores numbers and integers)
dmy<- dummyVars(" ~ .",data = mydata)
mydummifieddata<- data.frame(predict(dmy, newdata = mydata))
corMat = cor(mydummifieddata)
corMasterList<- flattenSquareMatrix(cor.prob(mydummifieddata)) ## list of all correlations
## order by strength of correlation
corlist<- corMasterList %>% arrange(-abs(corMasterList$cor))
write.csv(corlist,paste0("FLAT correlation matrix_.csv"))
corlist <- corlist %>% dplyr::filter(j=="FPH_EtOH") ## filter specific endpoint
head(corlist,50)
pairs.panels correlation matrix from library(psych)
## specify interesting variables:
interestingvariables<-c("ABW", "pounds_bbl", "daysonhops", "calcCO2vols_increase")
pairs.panels(mydummifieddata[c(interestingvariables, "FPH_EtOH")])

model
Observing the impacts of dryhopping in the presence of live yeast has led many brewing professionals to understand that FPH is a function of many of the variables above including hop variety,form_of_hops,harvestYear,OX,DH_temp,daysonhops,rouse,pounds_bbl…. Many have intuitively created a model in their heads (without necessarily thinking of it as such) and skillfully adjust process when necessary to account for this phenomenon. In linear modeling, our function will take on the form: \(FPH = intercept + \beta_{1}X_{1} + \beta_{2}X_{2} + ... + \beta_{n}X_{n}\) where \(\beta\) values are what we’re attempting to derive in this modeling exercise, and X values are (collectively) a particular set of conditions.
ActionItem: add link to modeling overview
linear models 1 (create models)
df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(df$SG~df$OE)
abline(lm1)
plot(df$SG~df$ADF)
abline(lm2)
plot(df$SG~df$ABW)
abline(lm3)
plot(df$SG~df$Ea)
abline(lm4)

linear models 2 (view residual plots)
df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(lm1$residuals)
plot(lm2$residuals)
plot(lm3$residuals)
plot(lm4$residuals)

#summary(lm4)
compare linear models using memisc package
df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
"(Intercept)" = "Constant",
x1 = "OE = Original Extract (g/100mL)",
x2 = "ADF = Apparent Degree of Fermentation (%)",
x3 = "ABW = Ethanol (w/w)",
x4 = "Er = Residual Extract (g/100mL)"
)
mtable1234
Calls:
Model 1: lm(formula = df$SG ~ df$OE)
Model 2: lm(formula = df$SG ~ df$ADF)
Model 3: lm(formula = df$SG ~ df$ABW)
Model 4: lm(formula = df$SG ~ df$Ea)
======================================================================
Model 1 Model 2 Model 3 Model 4
----------- ------------- ------------ ---------------
df$SG df$SG df$SG df$SG
----------------------------------------------------------------------
(Intercept) 1.071*** 1.063*** 1.056*** 1.000***
(0.020) (0.000) (0.001) (0.000)
df$OE -0.004**
(0.001)
df$ADF -0.001***
(0.000)
df$ABW -0.008***
(0.000)
df$Ea 0.004***
(0.000)
----------------------------------------------------------------------
sigma 0.002 0.000 0.001 0.000
R-squared 0.066 0.997 0.934 1.000
F 8.553 48038.147 1700.301 3532408.297
p 0.004 0.000 0.000 0.000
N 123 123 123 123
======================================================================
#show_html(mtable1234b)
compare more linear models
df <- FPHcalc
lm1<-lm(FPH_EtOH~daysonhops, data=df)
lm2<-lm(FPH_EtOH~daysonhops*form_of_hops, data=df)
lm3<-lm(FPH_EtOH~daysonhops*rouse, data=df)
lm4<-lm(FPH_EtOH~daysonhops*form_of_hops*rouse, data=df)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
"(Intercept)" = "Constant",
SG = "Specific Gravity",
ABW = "ABW = Ethanol (w/w)",
Er = "Er = Residual Extract (g/100mL)"
)
mtable1234
Calls:
Model 1: lm(formula = FPH_EtOH ~ daysonhops, data = df)
Model 2: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops, data = df)
Model 3: lm(formula = FPH_EtOH ~ daysonhops * rouse, data = df)
Model 4: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops * rouse, data = df)
=============================================================================
Model 1 Model 2 Model 3 Model 4
----------- ----------- ----------- -----------
FPH_EtOH FPH_EtOH FPH_EtOH FPH_EtOH
-----------------------------------------------------------------------------
(Intercept) 0.498*** 0.306 0.537*** 0.319
(0.044) (0.197) (0.049) (0.197)
daysonhops 0.044*** 0.044*** 0.041*** 0.041***
(0.003) (0.003) (0.004) (0.004)
form_of_hops: ground/cone 0.849** 0.849**
(0.278) (0.277)
form_of_hops: pellet/cone 0.174 0.199
(0.200) (0.199)
rouse -0.227 -0.208
(0.122) (0.118)
daysonhops x rouseTRUE 0.010 0.009
(0.006) (0.006)
-----------------------------------------------------------------------------
sigma 0.355 0.340 0.352 0.339
R-squared 0.634 0.669 0.645 0.678
F 210.023 80.145 72.038 49.193
p 0.000 0.000 0.000 0.000
N 123 123 123 123
=============================================================================
#show_html(mtable1234b)
The R Book (Crawley) Table 20.1: nonlinear functions useful in biology
Table 20.1. Useful non-linear functions EXPANDED:
| Asymptotic functions |
Michaelis–Menten |
\(y =\frac{ax}{1+bx}\) |
nls(bone~aage/(1+bage),start=list(a=8,b=0.08))) |
enzyme reactions |
|
|
|
nls(rate~SSmicmen(conc,a,b)) |
tbd |
|
2-parameter asymptotic exponential |
\(y = a(1 − e^{−bx} )\) |
nls(bone~a(1-exp(-cage)),start=list(a=120,c=0.064)) |
tbd |
|
3-parameter asymptotic exponential |
\(y = a − be^{−cx}\) |
nls(bone~a-bexp(-cage),start=list(a=120,b=110,c=0.064)) |
tbd |
|
|
|
nls(bone~SSasymp(age,a,b,c)) |
tbd |
|
|
|
nls(density ~ SSlogis(log(concentration), a, b, c)) |
tbd |
| S-shaped functions |
2-parameter logistic |
\(y = \frac{e^{a+bx}}{1 + e^{a+bx}}\) |
|
tbd |
|
3-parameter logistic |
\(y = \frac{a}{1 + be^{−cx}}\) |
|
tbd |
|
4-parameter logistic |
\(y = a + \frac{b-a}{1 + e^{(c−x)/d}}\) |
nls(weight~SSfpl(Time, a, b, c, d)) |
tbd |
|
Weibull |
\(y = a − be^{−(cx^d)}\) |
nls(weight ~ SSweibull(time, Asym, Drop, lrc, pwr)) |
tbd |
|
Gompertz |
\(y = ae^{−be^{−cx}}\) |
|
tbd |
| Humped curves |
Ricker curve |
\(y = axe^{−bx}\) |
|
tbd |
|
First-order compartment |
\(y = k exp(−exp(a)x) − exp(−exp(b)x)\) |
nls(conc~SSfol(Dose, Time, a, b, c)) |
tbd |
|
Bell-shaped |
\(y = a exp(−ABS(bx)^2)\) |
|
tbd |
|
Biexponential |
\(y = ae^{bx} − ce^{−dx}\) |
|
tbd |
Michaelis-Menton model (Crawley p. 741)
\(y =\frac{ax}{1+bx}\)
expt2 <- FPHcalc %>% dplyr::filter(expt==" 2A") %>% dplyr::select(FPH_EtOH, rouse, daysonhops,brew_date, pounds_bbl,form_of_hops,ABW.control_NH,dayofaddition)
mymodel <- nls(FPH_EtOH~a*daysonhops/(1+b*daysonhops), data=expt2,start=list(a=8, b=0.08))
summary(mymodel)
Formula: FPH_EtOH ~ a * daysonhops/(1 + b * daysonhops)
Parameters:
Estimate Std. Error t value Pr(>|t|)
a 0.186159 0.010109 18.41 < 2e-16 ***
b 0.067820 0.005728 11.84 1.45e-15 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.09307 on 46 degrees of freedom
Number of iterations to convergence: 6
Achieved convergence tolerance: 1.686e-06
x <- seq(0,50,0.1)
yv <- predict(mymodel, list(daysonhops=x))
{plot(expt2$daysonhops, expt2$FPH_EtOH, pch=21, col="purple", bg="green")
lines(x,yv,col="blue")}

So using the Michaelis-Menten equation as our model, the relationship between hop-induced ethanol production (FPH_EtOH) and hop contact time (daysonhops) would be expressed as: \(FPH_EtOH = \frac{0.183572*daysonhops}{1+0.066388*daysonhops}\)
nonlinear models of hop-induced Plato drop over time with library(drc)
df <- FPHcalc %>% filter(expt==" 2A")
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
group<- as.factor(df$special_conditions) #rouse
cols <- as.numeric(group)
legend.cols <- as.numeric(as.factor(levels(group)))
## Fitting models using function drm from library(drc). see <http://rstats4ag.org/dose-response-curves.html> for overview and <https://www.rdocumentation.org/packages/drc/> for list of models (fct values) and other functions available in drc
## create models
m.LL.3<-drm(y ~x,group, fct = LL.3()) #3-parameter logistic (lower limit at 0)
NaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs produced
#m.LL.3u<-drm(y ~x, fct = LL.3u()) #3-parameter logistic (upper limit at 1)
m.LL.4<-drm(y ~x,group, fct = LL.4()) #4-parameter log-logistic
# from ??LL.4: f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\log(e)))} or in another parameterisation (converting the term \log(e) into a parameter) f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}
m.L.4 <-drm(y ~x,group, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,group, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter
m.LL2.5<-drm(y ~x,group, fct = LL2.5()) #Generalised log-logistic
# from ??LL.2.5: f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}
m.LL.5<-drm(y ~x,group, fct = LL.5()) #5-parameter logistic
m.W1.4<-drm(y ~x,group, fct = W1.4()) #4-parameter Weibull1
NaNs produced
m.W2.4<-drm(y ~x,group, fct = W2.4()) #4-parameter Weibull2
m.BC.5<-drm(y ~x,group, fct = BC.5()) #5-parameter Brain-Cousens (hormesis)
m.AR.3<-drm(y ~x,group, fct = AR.3()) #3-parameter Shifted asymptotic regression
NaNs producedNaNs produced
#m.MM.2<-drm(y ~x,group, fct = MM.2()) #2-parameter Michaelis-Menten
m.MM.3<-drm(y ~x,group, fct = MM.3()) #3-parameter Michaelis-Menten
NaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs producedNaNs produced
##plot
#par(mfrow = c(1,2), oma = c(0, 0, 0, 0))
{plot(x , y, col=cols, main="raw data")
legend("topright", legend=levels(group), pch=16, col=legend.cols)}

plot(m.LL.3, type='all',col=cols, main="LL.3 (lower limit at 0)")

plot(m.LL.4, type='all',col=cols, main="LL.4 four-parameter log-logistic")

plot(m.L.4, type='all',col=cols, main="L.4")

plot(m.LL.5, type='all',col=cols, main="LL.5")

plot(m.LL2.4, type='all',col=cols, main="LL2.4 four-parameter log-logistic")

plot(m.LL2.5, type='all',col=cols, main="Generalised log-logistic")

plot(m.W1.4, type='all',col=cols, main="Weibull1")

plot(m.W2.4, type='all',col=cols, main="Weibull2")

plot(m.BC.5, type='all',col=cols, main="Brain-Cousens (hormesis)")

plot(m.AR.3, type='all',col=cols, main="Shifted asymptotic regression")

#plot(m.MM.2, type='all',col=cols, main="2-parameter Michaelis-Menten")
plot(m.MM.3, type='all',col=cols, main="3-parameter Michaelis-Menten")

Speers2003: Non‐Linear Modelling of Industrial Brewing Fermentations
R. Alex Speers, Peter Rogers, Bruce Smith J. Inst. Brew. 109(3), 229–235, 2003 https://doi.org/10.1002/j.2050-0416.2003.tb00163.x Free Access from the Institute of Brewing! https://onlinelibrary.wiley.com/doi/10.1002/j.2050-0416.2003.tb00163.x
Following Speers2003, the relationship between plato and time in primary fermentations is well modeled by a four parameter logistic model:
PLATO = PINF + (P_D / (1+ (EXP(-B*(TIME-M)))))
or
\(PLATO = P_{inf}+ \frac{P_D}{1+e^{-B(t-M)}}\)
where
- t = time into fermentation
- P_D = change in Plato during fermentation (OE - P_{inf})
- B ~ maximum fermentation rate
- M = fermenation midpoint
- P_inf = final gravity
so then for our case
\(PLATO = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)
PLATO = PINF + (P0 / (1+ (EXP(B*(HOURS-M)))))
MacIntosh2016: An Examination of Substrate and Product Kinetics During Brewing Fermentations
Andrew J. MacIntosh, Maria Josey, R. Alex Speers J. Am. Soc. Brew. Chem. 74(4), 250-257, 2016
“the five-parameter method was selected over the standard four-parameter logistic detailed in ASBC Method Yeast- 14 because it yielded a superior fit when assessed with an extra sum-of-squares F test. We further note that it is only statistically advantageous to apply the five-parameter model when many data points are available (72 from this experiment as opposed to 10 when using Yeast-14).”
MacIntosh: \(P_{(t)} =\frac{P_i - P_e}{(1+s * e^{-B(t-M)})^{1/s}}\)
library(drc) getMeanFunctions()
- drc LL.4: \(f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}\) #4param logistic
- drc LL.5: \(f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}\) #5param logistic
- drc G.4 \(f(x) = c + (d-c)(\exp(-\exp(b(x-e))))\) #4param Gompertz
- drc W1.4 \(f(x) = c + (d-c) \exp(-\exp(b(\log(x)-\log(e))))\) #4param Weibull1
- drc W2.4 \(f(x) = c + (d-c) (1 - \exp(-\exp(b(\log(x)-\log(e)))))\) #4param Weibull2
multilevel logistic models with library(drc)
df <- FPHcalc %>% filter(expt==" 2A")
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
mylevels<- as.factor(df$special_conditions) #rouse
cols <- as.numeric(mylevels)
legend.cols <- as.numeric(as.factor(levels(mylevels)))
## create models
#m.LL.4<-drm(y ~x,mylevels, fct = LL.4(names = c("Slope", "Lower", "Upper", "Midpoint or ED50"))) #4-parameter log-logistic (general parameters)
#m.LL.4<-drm(y ~x,mylevels, fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50"))) #4-parameter log-logistic (Dose-Response parameters)
m.LL.4<-drm(y ~x,mylevels, fct = LL.4(names = c("F_max", "P_inf", "OE", "M"))) #4-parameter log-logistic
m.L.4 <-drm(y ~x,mylevels, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,mylevels, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter
m.LL2.5<-drm(y ~x,mylevels, fct = LL2.5()) #Generalised log-logistic
m.LL.5<-drm(y ~x,mylevels, fct = LL.5()) #5-parameter logistic
mymodel <- m.LL.4
confint(mymodel)
2.5 % 97.5 %
F_max:rouse 1.186734 1.597836
F_max:still 1.537194 2.063586
P_inf:rouse 1.653949 1.963275
P_inf:still 2.079132 2.219179
OE:rouse 3.710521 3.787753
OE:still 3.714969 3.786819
M:rouse 10.616221 13.931883
M:still 7.316189 8.600129
{plot(mymodel,log="", broken = TRUE, bcontrol = list(style = "slash"), col = c(2,6,3,23,56), main = "impact of mylevels on estimated model paramaters")
legend(34, 3.7, legend=c("roused","still"), fill=c("red", "pink"), title="mylevels")}

mymodel
A 'drc' model.
Call:
drm(formula = y ~ x, curveid = mylevels, fct = LL.4(names = c("F_max", "P_inf", "OE", "M")))
Coefficients:
F_max:rouse F_max:still P_inf:rouse P_inf:still OE:rouse OE:still
1.392 1.800 1.809 2.149 3.749 3.751
M:rouse M:still
12.274 7.958
summary(mymodel)
Model fitted: Log-logistic (ED50 as parameter) (4 parms)
Parameter estimates:
Estimate Std. Error t-value p-value
F_max:rouse 1.392285 0.101704 13.690 < 2.2e-16 ***
F_max:still 1.800390 0.130226 13.825 < 2.2e-16 ***
P_inf:rouse 1.808612 0.076525 23.634 < 2.2e-16 ***
P_inf:still 2.149156 0.034647 62.030 < 2.2e-16 ***
OE:rouse 3.749137 0.019107 196.219 < 2.2e-16 ***
OE:still 3.750894 0.017775 211.018 < 2.2e-16 ***
M:rouse 12.274052 0.820272 14.963 < 2.2e-16 ***
M:still 7.958159 0.317638 25.054 < 2.2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error:
0.03875737 (40 degrees of freedom)
Each level (rouse and still) has a unique curve shape and therefore different parameters for a best-fit log-logistic model. Now that we know this, it is practical to address each set of conditions separately, and based on the LL.4 model above we have:
for rouse = TRUE:
\(PLATO_{roused} = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)
or
\(PLATO_{roused} = 1.809 + \frac{1.94}{1+e^{-1.3925(daysonhops-12.272)}}\)
and
\(PLATO_{still} = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)
Plotting and analysis options for multilevel models are limited, so let’s move forward with a single set of conditions and a ‘simple’ single-level logistic model (expt=2a, rouse=TRUE).
unilevel logistic model with confidence intervals
df <- FPHcalc %>% filter(expt==" 2A" & rouse==TRUE)
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- as.numeric(df$daysonhops)
y <- df$PLATO.hopdrop
## create model
mymodel<-drm(y ~x, fct = LL.4()) #4-parameter logistic with library(drc)
plot(mymodel, main = "default")

plot(mymodel, log="", main = "non-logarithmic x-axis")

# create prediction intervals
newdata = data.frame(y = y, x = x)
conf95 <- as.data.frame(predict(mymodel, newdata,interval = "confidence", level = 0.95))
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
conf95$x <- x
pred95 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.95))
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
pred95$x <- x
pred999 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.999))
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
Recycling array of length 1 in array-vector arithmetic is deprecated.
Use c() or as.vector() instead.
pred999$x <- x
# Asymmetric ribbons based on prediction intervals
#newdata <- data.frame(y = c(3.5, 3, 2.9, 2.5,1.8,1.7), x = c(1,5,10,20,30,40))
#newdata <- tidyfermi %>% filter(batch = SELECTBATCH) %>% select(days,plato)
qplot(data=pred95, x=x, y=Prediction, ymin=Lower, ymax=Prediction, geom="ribbon", fill=I("red"), alpha=I(0.2)) +
geom_ribbon(data=pred95, aes(x=x, ymin=Prediction, ymax=Upper), fill=I("blue"), alpha=I(0.2)) +
geom_line(data=pred95, aes(x=x, y=Prediction), color=I("green"), lwd=1)+
geom_point(data=newdata, aes(x=x, y=y, ymin=NULL, ymax=NULL), size=1, col="blue")+
ylab("y")

# Visualise intervals
# based on Maurits Evers (https://stackoverflow.com/questions/49444489/nonlinear-regression-prediction-in-r?rq=1)
data.frame(x=x, y=y) %>% ggplot(aes(x, y)) +
geom_point() +
geom_line(data = pred95, aes(x = x, y = Prediction)) +
geom_ribbon(data = pred999, aes(x = x, ymin = Lower, ymax = Upper),fill=I("pink"),alpha = 0.4)+
geom_ribbon(data = pred95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("green"),alpha = 0.4)+
geom_ribbon(data = conf95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("purple"),alpha = 0.4);

Note: If you compare the multi-level model parameter estimates for rouse=TRUE with those for unilevel model based on rouse=TRUE subset, the results are quite similar, but not identical!
plot residuals of nonlinear models generated in library(drc)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(m.MM.3$predres)
plot(m.LL.3$predres)
plot(m.LL.4$predres)
plot(m.LL.5$predres)

summary
For cases where 2016 Centennial hops are added to this particular base beer (sampled into 12oz bottles) near the end of primary fermentation at room temperature and bottles remain untouched (still), the 4-parameter logistic equation with the following sets of parameters is a reasonable model of the subsequent hop-induced plato drop:
\(PLATO_{still} = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)
The following 4-parameter logistic equation is a reasonable model for cases identical to the above, but where bottles are roused once daily:
\(PLATO_{roused} = 1.809 + \frac{1.94}{1+e^{-1.3925(daysonhops-12.272)}}\)
Whereas the residual sum of squares for the 5-parameter logistic models was similar to those obtained for the 4-parameter equation, a visual inspection reveals that the 4-parameter equation is probably superior, in that the 4-parameter fit apparently does not [falsely?] predict a long lag time in the case of unroused samples.
Only a single variable (rousing vs. still) among several hundred variables encompassing raw materials, machines, people and process resulted in quite different estimated parameters for a 4-parameter logistic model. These results highlight the fact that all process variables that significantly impact the endpoints we wish to control must themselves be defined, controlled and/or documented if models such as these are to be used to characterize the process in question.
bigchunk_tidytransform
mydata <-read.csv("ujbc_a_1469081_sm5496.txt",stringsAsFactors = FALSE) ## SPECIFY filename
## fill in MISSING DATA :( add time zero data for time series analysis)
## nested ifelse to add time zero plato values for each expt:
mydata<- mydata %>% mutate(., initial_plato = ifelse(expt==" 2A", 3.74, ifelse(expt==" 1A", 3.69, ifelse(expt==" 1B", 3.53, ifelse(expt==" 3B", 3.54, ifelse(expt==" 4B", 4.66, NA))))))
## add complete time-zero anton paar data for time series (expt2A)
## quadruple-up each triplicate time-zero measurement (one copy for each NH/DH & rouse/still combo)
## note: this practice is of questionable statistical rigor. In retrospect it would have been better (and a lot easier) to just run 12 samples through Anton on day0!
timezero <- mydata %>% filter(expt==" 2A") %>% arrange(special_group) %>% slice(1:12)
#data.entry(timezero)
timezero$Test_Date <- rep(c("7/27/2017","7/27/2017","7/27/2017"),4)
timezero$Test.time <- rep(c("3:26 PM", "3:30 PM", "3:34 PM"),4)
timezero$ABV <- 6.68
timezero$ABW <- 5.2
timezero$OE <- 15.95
timezero$Er <- 6.08
timezero$Ea <- rep(c(3.74, 3.73, 3.74),4)
timezero$SG <- rep(c(1.01462, 1.01461, 1.01462),4)
timezero$RDF <- rep(c(63.88, 63.89, 63.88),4)
timezero$ADF <- rep(c(76.58, 76.60, 76.58),4)
timezero$Calories <- rep(c(215.37,215.34,215.35),4)
timezero[timezero$hop=="DH",]$contact_days <-0
timezero[,c(28:31)] <- 0
mydata <- rbind(timezero,mydata)
write.csv(mydata, "quickie.csv", row.names = FALSE)
mydata<-read.csv("quickie.csv", stringsAsFactors = FALSE)
### this is a routine for inspecting data ###
mydatatypes<- as.data.frame(cbind(sapply(mydata, class))) ## table of datatypes
mydatatypes$headers <- rownames(mydatatypes) ## convert rownames to values
na_count <-as.data.frame(sapply(mydata, function(y) sum(length(which(is.na(y)))))) ## count NAs by column
mydataOverview<-as.data.frame(cbind(na_count,mydatatypes)) ## table of NA counts and datatypes
names(mydataOverview) <- c("NA_count","Data_Class", "Header")
sum(is.na(mydata)) ## total count of NA values in entire sheet (231 in this case)
[1] 261
mydataOverview %>% filter(NA_count>0) ## breakdown of NA values
datecols<- dput(names(dplyr::select(mydata, matches("date")))) ## headers containing string "date" => c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date")
c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date"
)
## FORLOOP
for (icol in datecols) {
newcol = paste0(icol,".POSIXct")
# print(newcol)
mydata[, newcol] = as.POSIXct(mydata[, icol],format = "%m/%d/%Y") ### CREATE NEW columns with POSIXct
# mydata[, newcol] = as.POSIXct(as.numeric(mydata[, icol]) * (60*60*24), origin="1899-12-30") ### microsoft times
}
## create dayofaddition, daysonhops, hops_g_100mL, pounds_bbl variables with dplyr "mutate"
mydata<- mydata %>%
mutate(dayofaddition = as.integer(as.numeric(difftime(dryhop_date.POSIXct,brew_date.POSIXct))),
daysonhops = as.integer(as.numeric(difftime(Test_Date.POSIXct,dryhop_date.POSIXct))/(60*60*24)),
hops_g_100mL = (mg_hops/volume_mL)/10,
pounds_bbl = (mg_hops/volume_mL)*117/454
)
mydata$OX<-grepl("OX", mydata$Hop_type) ## create logical "OX" column
mydata$rouse<-grepl("rouse", mydata$special_group)## create logical column
mydata$Grind<-grepl("Grind", mydata$Hop_type) ## create logical column
mydata$Cone<-grepl("Cone", mydata$Hop_type) ## create logical column
mydata$harvest2014<-grepl("14", mydata$Hop_type) ## create logical column
mydata$harvest2015<-grepl("15", mydata$Hop_type) ## create logical column
mydata$harvest2017<-grepl("17", mydata$Hop_type) ## create logical column
## ifelse statement for harvestyear (if neither 2014 nor 2015 nor 2017, then 2016)
mydata$harvestYear <- ifelse(
mydata$harvest2014==TRUE, 2014,
ifelse(mydata$harvest2015==TRUE, 2015,
ifelse(mydata$harvest2017==TRUE, 2017, 2016)))
## ifelse statement for form of hops (if neither cone nor ground nor NH, then pellet)
mydata$form_of_hops <- ifelse(
mydata$Cone==TRUE, "cone",
ifelse(mydata$Grind==TRUE, "ground",
ifelse(mydata$Hop_type=="NH", "NH", "pellet")))
## ifelse statement for temperature greater or less than 10
mydata$DH_temp <- ifelse(
mydata$temp.C<10, "cold",
ifelse(mydata$temp.C>10, "warm", "something else"))
mydata$variety<-mydata$Hop_type
mydata$variety<- gsub("[0-9]+","", mydata$variety) ## remove all numbers
mydata$variety<- gsub("OX","", mydata$variety) ## remove specific text
mydata$variety<- gsub("Grind","", mydata$variety)
mydata$variety<- gsub("Cone","", mydata$variety)
mydata$variety<- gsub(" ","", mydata$variety) ## remove spaces
mydata<- mydata %>% mutate(EXPTnew=paste0("group",expt, substr(special_group, 1,2),as.character(rouse)))
# clean it up by removing "NA" and any spaces due to canarycode bug
mydata$EXPTnew <- gsub(" ","", mydata$EXPTnew) ## remove any spaces
mydata$EXPTnew <- gsub("NA","", mydata$EXPTnew) ## remove "NA"
## select and rearrange columns
mydata <- mydata %>%
dplyr::select(sample_id,expt, EXPTnew, hop, BINhop, variety, OX, harvestYear, form_of_hops,special_conditions, rouse, daysonhops, dayofaddition, DH_temp, temp.C, hops_g_100mL, pounds_bbl,
ABV, ABW, OE, Er, Ea, SG, RDF, ADF, Calories,
dhop_day, contact_days, REF_NH, ABV_increase,
brew_date.POSIXct, sample_collection_date.POSIXct, dryhop_date.POSIXct,Test_Date.POSIXct, initial_plato)
## remove ".POSIXct" suffix. Leaving it as-is will only add to confusion if/when these data are saved and re-imported (and become 'character' format!)
colnames(mydata) = gsub(".POSIXct", "", colnames(mydata))
##compute mean NH (control) values
## mean NH (control) values for each EXPTnew group
mean.control_NH<- mydata %>%
group_by(EXPTnew) %>%
filter(hop=="NH") %>%
summarise_at(vars(ABV, ABW, Ea, SG),funs(mean, n()))
## replace "_mean" with ".control_NH" in column names
colnames(mean.control_NH) = gsub("_mean", ".control_NH", colnames(mean.control_NH))
##compute $\Delta$ values (changes relative to un-dryhopped controls) using objects created above...
## first join our data with mean ABV for unhopped samples in given experiment (mean.control_NH; calculated above)
FPHcalc<- left_join(mydata, mean.control_NH, by="EXPTnew")
## calculate ABW_increase by subtracting each individual ABW measurement from respective mean.control_NH:
FPHcalc$delta.ABV <- FPHcalc$ABV - FPHcalc$ABV.control_NH
FPHcalc$delta.ABW <- FPHcalc$ABW - FPHcalc$ABW.control_NH
FPHcalc$delta.plato <- FPHcalc$Ea - FPHcalc$Ea.control_NH
FPHcalc$delta.SG <- FPHcalc$SG - FPHcalc$SG.control_NH
## the control samples have served their purpose, now remove them from dataset. The following calculations are only meaningful for dry-hopped samples.
FPHcalc<- FPHcalc %>% filter(hop=="DH")
## *calcalate* corresponding CO2 production
FPHcalc$calcCO2_increase <- FPHcalc$delta.ABW*(0.44/0.46)
##convert calcCO2_increase (in g/100mL) to calculated CO2 volumes added
## g/L = 10* g/100mL
## The conversion factor from volumes of CO2 to CO2 by weight (g/L) is 1.96. For example: 2.5 volumes x 1.96 = 4.9 g/l.
FPHcalc$calcCO2vols_increase <- FPHcalc$calcCO2_increase*10/1.96
## define "FPH" as amount produced per % dry-hops added (in g/100mL):
## FPH = Fold Production due to Hops (fold-increase by mass: amount of given endpoint relative to amount of hops added)
##
FPHcalc$FPH_EtOH = FPHcalc$delta.ABW/FPHcalc$hops_g_100mL
FPHcalc$FPH_CO2 = FPHcalc$calcCO2_increase/FPHcalc$hops_g_100mL
FPHcalc$FPH_plato = FPHcalc$delta.plato/FPHcalc$hops_g_100mL
## replacing time-zero time values with a very small number (rather than exactly zero) will prevent issues with analysis of nonlinear models
FPHcalc[FPHcalc$daysonhops==0,]$daysonhops <- 0.01
## and save the transformed data to csv:
write.csv(FPHcalc,"FPHcalc.csv", row.names = FALSE)
FPHcalc <- read.csv("FPHcalc.csv", stringsAsFactors = TRUE)
df<- FPHcalc %>% group_by(EXPTnew) %>%
summarise_at(vars(hops_g_100mL,pounds_bbl,delta.ABV, delta.ABW, delta.plato, FPH_plato, FPH_EtOH, FPH_CO2, calcCO2vols_increase),funs(round(mean(.), 2))) %>%
arrange(desc(FPH_EtOH))
df
sessionInfo()
R version 3.5.2 (2018-12-20)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)
Matrix products: default
locale:
[1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252
[3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C
[5] LC_TIME=English_United States.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] bindrcpp_0.2.2 plotly_4.8.0 drc_3.0-1 memisc_0.99.14.12
[5] MASS_7.3-51.1 psych_1.8.4 caret_6.0-81 lattice_0.20-38
[9] forcats_0.3.0 stringr_1.3.1 purrr_0.2.4 readr_1.1.1
[13] tidyr_0.8.0 tibble_1.4.2 tidyverse_1.2.1 gridExtra_2.3
[17] ggplot2_3.0.0 dplyr_0.7.4
loaded via a namespace (and not attached):
[1] nlme_3.1-137 lubridate_1.7.4 httr_1.3.1 rprojroot_1.3-2
[5] repr_0.19.1 tools_3.5.2 backports_1.1.2 R6_2.2.2
[9] rpart_4.1-13 lazyeval_0.2.1 colorspace_1.3-2 nnet_7.3-12
[13] withr_2.1.2 tidyselect_0.2.4 mnormt_1.5-5 curl_3.2
[17] compiler_3.5.2 cli_1.0.0 rvest_0.3.2 xml2_1.2.0
[21] sandwich_2.5-0 labeling_0.3 scales_0.5.0 mvtnorm_1.0-10
[25] digest_0.6.15 foreign_0.8-71 rmarkdown_1.9 rio_0.5.16
[29] base64enc_0.1-3 pkgconfig_2.0.1 htmltools_0.3.6 plotrix_3.7-4
[33] htmlwidgets_1.3 rlang_0.3.1 readxl_1.1.0 rstudioapi_0.7
[37] shiny_1.1.0 bindr_0.1.1 generics_0.0.2 zoo_1.8-4
[41] jsonlite_1.5 crosstalk_1.0.0 gtools_3.8.1 ModelMetrics_1.2.2
[45] zip_1.0.0 car_3.0-2 magrittr_1.5 Matrix_1.2-15
[49] Rcpp_0.12.16 munsell_0.4.3 abind_1.4-5 stringi_1.1.7
[53] multcomp_1.4-10 yaml_2.1.19 carData_3.0-2 plyr_1.8.4
[57] recipes_0.1.4 grid_3.5.2 promises_1.0.1 parallel_3.5.2
[61] crayon_1.3.4 haven_1.1.1 splines_3.5.2 hms_0.4.2
[65] knitr_1.21 pillar_1.2.2 reshape2_1.4.3 codetools_0.2-15
[69] stats4_3.5.2 glue_1.2.0 evaluate_0.10.1 data.table_1.12.0
[73] modelr_0.1.2 httpuv_1.4.3 foreach_1.4.4 cellranger_1.1.0
[77] gtable_0.2.0 assertthat_0.2.0 xfun_0.4 gower_0.1.2
[81] openxlsx_4.1.0 mime_0.5 prodlim_2018.04.18 xtable_1.8-2
[85] broom_0.4.4 later_0.7.2 viridisLite_0.3.0 class_7.3-14
[89] survival_2.43-3 timeDate_3043.102 iterators_1.0.10 lava_1.6.4
[93] TH.data_1.0-10 ipred_0.9-8
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2s6IE1vZGVsaW5nIHRoZSBGcmVzaGVuaW5nIFBvd2VyIG9mIHRoZSBIb3AiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo+ICpTdGVwMTogIE9wZW4gdGhpcyAuUk1EIGZpbGUgaW4gUiBTdHVkaW8gYW5kIGNsaWNrIHRoZSAiUHJldmlldyIgYnV0dG9uIGFib3ZlLiAgQSBmb3JtYXR0ZWQgdmVyc2lvbiBzaG91bGQgcG9wIHVwIGluIGEgYnJvd3NlciB3aW5kb3cuICBJRiBub3QsIHRyb3VibGVzaG9vdCB0aGF0ISoNCg0KDQojIG1hbnVhbCBvdXRsaW5lDQoNCjEuIEhvdXNla2VlcGluZw0KICAgIDEuICpTRVQgV09SS0lORyBESVJFQ1RPUlkqIA0KICAgIDEuIHRoaXMgKipSIE1hcmtkb3duICguUk1EKSoqIGRvY3VtZW50IHdhcyBjcmVhdGVkIHVzaW5nIFIgU3R1ZGlvDQogICAgICAgIDEuIHBsYWluIHRleHQNCiAgICAgICAgMS4gIlJlcHJvZHVjaWJsZSBSZXNlYXJjaCIgTW92ZW1lbnQNCiAgICAxLiAqKipSKioqIGZyb20gPGh0dHBzOi8vd3d3LnItcHJvamVjdC5vcmcvPg0KICAgIDEuICoqKlIgU3R1ZGlvKioqIGZyb20gPGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvPg0KICAgIDEuIEVudmlyb25tZW50IHRhYiAoUnN0dWRpbykNCiAgICAxLiBGaWxlcy9QbG90cy9QYWNrYWdlcy9IZWxwL1ZpZXdlciAgdGFiIChSc3R1ZGlvKQ0KICAgIDEuIFByZXZpZXcvdmlldyBhcyBIVE1MICAoUiBNYXJrZG93biBpbiBSc3R1ZGlvKQ0KICAgIDEuIE91dGxpbmUgdGFiIChSIE1hcmtkb3duIGluIFJzdHVkaW8pDQogICAgDQoqIEludHJvDQogICAgKyBTdGF0ZW1lbnQgb2YgUHJvYmxlbQ0KICAgICsgT3ZlcnZpZXcgb2YgRXhwZXJpbWVudGFsIERhdGENCiAgICArIE1lYXN1cmluZyB0aGUgRnJlc2hlbmluZyBQb3dlciBvZiB0aGUgSG9wIChyZWxhdGl2ZSB0byBub24tZHJ5aG9wcGVkIGNvbnRyb2xzKQ0KICAgICsgKmNhbGN1bGF0ZWQgbWV0cmljcyoNCiAgICAgICAgLSAkXERlbHRhJEFCViANCiAgICAgICAgLSAkXERlbHRhJEFCVw0KICAgICAgICAtICRcRGVsdGF7Y2FsY3VsYXRlZH0kQ08kXzIkDQogICAgICAgIC0gJFxEZWx0YSQgJmRlZztwbGF0bw0KICAgICAgICAtICRcRGVsdGEkU0ckXnsyMC8yMH0kDQogICAgICAgIC0gZm9sZC1pbmNyZWFzZSBldGhhbm9sIGZyb20gaG9wIGFkZGl0aW9uIChpbiAkXGZyYWN7cG91bmRzfXtiYmx9JCBhbmQgd3QlIG9yICRcZnJhY3tncmFtc317MTAwIG1MfSQpDQogICAgICAgIC0gZm9sZC1pbmNyZWFzZSBDTyRfMiQgZnJvbSBob3AgYWRkaXRpb24gKGluIGcvTCBhbmQgQ08kXzIkIHZvbHVtZXMpDQoqIExvYWQgTGlicmFyaWVzDQoqIERhdGEgSW1wb3J0LCBUaWR5IGFuZCBUcmFuc2Zvcm0NCiogRGF0YSBWaXN1YWxpemF0aW9uDQogICAgKyBzY2F0dGVyIHBsb3RzDQogICAgKyBzY2F0dGVyIHBsb3RzIHdpdGggYWRkZWQgZGltZW5zaW9ucw0KKiBNb2RlbGluZyBvdmVydmlldw0KICAgICsgbGluZWFyIHJlZ3Jlc3Npb24NCiAgICArIHJlc2lkdWFscw0KKiBNb2RlbGluZyBGUEgNCg0KDQojIGludHJvDQoNCiogWyoiRGF0YSBpcyBub3QgaW5mb3JtYXRpb24uIEluZm9ybWF0aW9uIGlzIG5vdCBrbm93bGVkZ2UuIEFuZCBrbm93bGVkZ2UNCmlzIGNlcnRhaW5seSBub3Qgd2lzZG9tLiIqXShodHRwOi8vd3d3LmRhdGFnb3Zlcm5hbmNlLmNvbS9xdW90ZXMva25vd2xlZGdlLXF1b3Rlcy8gIkNsaWZmb3JkIFN0b2xsIikNCiogW1dpY2toYW0ncyBmb3VyIHBpbGxhcnMgb2YgZGF0YSBhbmFseXNpczpdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovICJHcm9sZW11bmQgYW5kIFdpY2toYW0ncyBSIGZvciBEYXRhIFNjaWVuY2UiKQ0KICAgICogdGlkeQ0KICAgICogdHJhbnNmb3JtDQogICAgKiB2aXN1YWxpemUNCiAgICAqIG1vZGVsDQoNCmFzY2lpIGZvbnQvYXJ0IGZyb20gPGh0dHA6Ly93d3cubmV0d29yay1zY2llbmNlLmRlL2FzY2lpLz4NCmBgYHtyIGVjaG89VFJVRX0NCiMgICAgICAgICAgICAgICAgICAgICAgIF9fICAuX18gICAgLl9fXyAgICAgIA0KIyAgICAgICAgICAgICAgICAgICAgIF8vICB8X3xfX3wgX198IF8vX18uX18uDQojICAgICAgICAgICAgICAgICAgICAgXCAgIF9fXCAgfC8gX18gPCAgIHwgIHwNCiMgICAgICAgICAgICAgICAgICAgICAgfCAgfCB8ICAvIC9fLyB8XF9fXyAgfA0KIyAgICAgICAgICAgICAgICAgICAgICB8X198IHxfX1xfX19fIHwvIF9fX198DQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcL1wvICAgICANCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcLw0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHx8DQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfHwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8fA0KIyAgICAgICAgICAgICAgXyAgICAgICAgICAgICAgICAgICAgIC9cICAgICAgICAgICAgICAgDQojICAgICAgICAgICAgIHwgfF8gXyBfIF9fIF8gXyBfICBfX18vIF98X19fIF8gXyBfIF9fICANCiMgICAgICAgICAgICAgfCAgX3wgJ18vIF9gIHwgJyBcKF8tPCAgXy8gXyBcICdffCAnICBcIA0KIyAgICAgICAgICAgICAgXF9ffF98IFxfXyxffF98fF8vX18vX3wgXF9fXy9ffCB8X3xffF98DQojICAgICAgICAgICAgICAgICAgICAgICAgLy8gICAgICAgICAgICAgICAgICAgICAgXFwNCiMgICAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgICAgXFwNCiMgICAgICAgICAgICAgICAgICAgICAgLy8gICAgICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgICAgICAgICAgICAgLy8gICAgICAgICAgICAgICAgICAgICAgICAgICAgXFwNCiMgICAgICAgICAgXyAgICAgICAgIC8vICAgXyBfICAgICAgICAgICAgICAgICAgICAgICAgXFwgICBfICAgICBfIA0KIyAgICAgX18gXyhfKV9fX18gIF8gX18gX3wgKF8pX19fX19fICAgICAgICBfIF9fICBfX18gIF9ffCB8X19ffCB8DQojICAgICBcIFYgLyAoXy08IHx8IC8gX2AgfCB8IHxfIC8gLV8pPT09PT09fCAnICBcLyBfIFwvIF9gIC8gLV8pIHwNCiMgICAgICBcXy98Xy9fXy9cXyxfXF9fLF98X3xfL19fXF9fX3wgICAgICB8X3xffF9cX19fL1xfXyxfXF9fX3xffA0KYGBgDQoNClRoaXMgaXMgcHJpbWFyaWx5IGFuIGV4Y2VyY2lzZSBpbiBkYXRhIHdyYW5nbGluZyAodGlkeSt0cmFuc2Zvcm0pIGFuZCBtYXJrZG93biBmb3JtYXR0aW5nIHVzaW5nIFIgU3R1ZGlvLiAgSW4gdGhhdCBjb250ZXh0LCB0aGlzIGFuYWx5c2lzIGlzIGEgKnByZWxpbWluYXJ5KiBleHBsb3JhdGlvbiBvZiBwb3RlbnRpYWwgbWV0cmljcyBmb3IgKkhvcCBGcmVzaGVuaW5nIFBvd2VyKiAoc3lub255bXM6ICdkcnlob3AgY3JlZXAnLCAnQUJWIGNyZWVwJywgJ2RyeS1ob3AgY3JlZXAnKSBhbmQgdG8gbW9kZWwgdGhpcyBhc3BlY3Qgb2YgdGhlIHdhcm0tZHJ5aG9wcGluZyBwcm9jZXNzLiANCg0KIyMgU3RhdGVtZW50IG9mIFByb2JsZW0NClRoZSB3YXJtLWRyeWhvcHBpbmcgcHJvY2VzcyBjYW4gcHJvZm91bmRseSBpbXBhY3QgdGhlIGJvZHksIGFsY29ob2wgYW5kIHZpY2luYWwgZGlrZXRvbmUgY29udGVudCBvZiB0aGUgYmVlci4gIEEgY29tbW9uIG1ldHJpYyBmb3IgdGhpcyBwaGVub21lbm9uIGhhcyBub3QgYmVlbiBlc3RhYmxpc2hlZC4NCg0KIyMgT3ZlcnZpZXcgb2YgRXhwZXJpbWVudGFsIERhdGENClRocm91Z2ggZXhwZXJpZW5jZSBpbiBicmV3aW5nIHdlIGtub3cgdGhhdCAqKmVuZHBvaW50cyoqIG9mIGRyeWhvcHBpbmcgaW5jbHVkZToNCg0KKiBmbGF2b3IgaW1wYWN0IChhbHdheXMpDQoqIGltcGFjdCBvbiB2aXN1YWwvcHJlc2VudGF0aW9uIChzb21ldGltZXMpDQoqICpldGhhbm9sIGluY3JlYXNlKiBhY2NvbXBhbmllZCBieSBkZWNyZWFzZSBpbiBzcGVjaWZpYyBncmF2aXR5IChzb21ldGltZXMpDQoqIENPMiBpbmNyZWFzZSAoc29tZXRpbWVzKQ0KKiBkaWFjZXR5bC9WREsgaW5jcmVhc2UgKHNvbWV0aW1lcykNCiogYW5kIHNvIG9uLi4uDQoNCkV4cGVyaW1lbnRzIHdlcmUgY2FycmllZCBvdXQgd2hlcmUgDQoNCjEuIG11bHRpcGxlIHJlcGxpY2F0ZSAyNTAtbUwgc2FtcGxlcyBvZiBbbmVhcmx5XSBlbmQtZmVybWVudGVkIGJlZXIgd2VyZSBjb2xsZWN0ZWQgZnJvbSBmZXJtZW50ZXJzIGludG8gMTJveiBhbWJlciBib3R0bGVzDQoxLiB0aGUgc2FtcGxlcyB3ZXJlIHJhbmRvbWl6ZWQgaW50byBncm91cHMgb2YgdGhyZWUsIHRoZW4gZWFjaCBncm91cCB3YXMgZWl0aGVyDQogICAgKiBkcnktaG9wcGVkIGF0IH4xLjAgcG91bmRzL2JibCAodHJlYXRtZW50KSwgb3INCiAgICAqIG5vdCBkcnktaG9wcGVkIChjb250cm9sKQ0KMS4gYWxsIHdlcmUgaGFuZC1jcmltcGVkIHdpdGggZm9pbCwgc3RvcmVkIGZvciB2YXJpb3VzIHRpbWVzIChtb3N0bHkgYXQgcm9vbSB0ZW1wLCBhIGZldyBpbiB0aGUgY29sZCksIHRoZW4gdGVzdGVkIG9uIGFuIEFudG9uIFBhYXIgRE1BNDUwMC9BbGNvbHl6ZXIgY2xhc3NpYw0KICAgICAgICANCkZvY3VzaW5nIG9uICpldGhhbm9sIGluY3JlYXNlIGFzIHRoZSBlbmRwb2ludCosIHdlIGtub3cgdGhlc2UgdG8gYmUgKipyZWxldmFudCBmYWN0b3JzKio6DQoNCiogdmFyaWV0eSBvZiBob3BzIChmaXZlIGRpZmZlcmVudCB2YXJpZXRpZXMpDQoqIHByZXNlbmNlIG9mIGxpdmUgeWVhc3QgKHRydWUgaW4gYWxsIGNhc2VzIGZvciB0aGVzZSBkYXRhKQ0KKiB0ZW1wLkMgPSB0ZW1wZXJhdHVyZSBkdXJpbmcgZHJ5aG9wcGluZyAobW9zdGx5IHdhcm0sIHdpdGggYSBmZXcgaW4gdGhlIGNvbGQpDQoqIGFtb3VudCBvZiBjb250YWN0IHRpbWUgYmV0d2VlbiBob3BzIGFuZCBiZWVyICh1cCB0byA3IHdlZWtzKQ0KDQoqRm9yIG1vcmUgZGV0YWlscyBvbiB0aGlzIGRhdGFzZXQgc2VlIHRoZSBbbWFudXNjcmlwdF0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2RvaS9mdWxsLzEwLjEwODAvMDM2MTA0NzAuMjAxOC4xNDY5MDgxP3Njcm9sbD10b3AmbmVlZEFjY2Vzcz10cnVlICJLaXJrZW5kYWxsMjAxOCIpLiAgDQoqIHBsZWFzZSBzZW5kIGNvbXBsYWludHMgYW5kIGNvcnJlY3Rpb25zIHRvIGx1a2UuY2hhZHdpY2tAZ21haWwuY29tIQ0KKiBEYXRhIGFyZSBmcm9tIHRoZSBmaWxlICJ1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0IiAoc3VwcGxlbWVudGFyeSBkYXRhIGZyb20gSmFjb2IgQS4gS2lya2VuZGFsbCwgQ2FydGVyIEEuIE1pdGNoZWxsICYgTHVjYXMgUi4gQ2hhZHdpY2sgKDIwMTgpOiBUaGUgRnJlc2hlbmluZyBQb3dlciBvZiBDZW50ZW5uaWFsIEhvcHMsICpKb3VybmFsIG9mIHRoZSBBbWVyaWNhbiBTb2NpZXR5IG9mIEJyZXdpbmcgQ2hlbWlzdHMqIFZvbHVtZSA3NiwgSXNzdWUgMywgUGFnZXMgMTc4LTE4NCAoKioyMDE4KiopIERPSTogMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODEqIGF2YWlsYWJsZSBmcm9tIDxodHRwczovL3d3dy50YW5kZm9ubGluZS5jb20vZG9pL2Z1bGwvMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODE/c2Nyb2xsPXRvcCZuZWVkQWNjZXNzPXRydWU+DQoNCg0KIyBsb2FkIGxpYnJhcmllcw0KYGBge3IgcGFja2FnZS5sb2FkLCByZXN1bHRzPSdhc2lzJywgZWNobz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0gDQojLnJzLnJlc3RhcnRSKCkgICMgcmVzdGFydCBSIA0Kc2Vzc2lvbkluZm8oKQ0KDQojZGF0YSB3cmFuZ2xpbmcNCiNpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpICAjIGRwbHlyICJkYXRhIHBseWVyIiAgDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShncmlkRXh0cmEpICAjIGZvciBncmlkLmFycmFuZ2UgaW4gUiBtYXJrZG93bg0KDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQoNCmxpYnJhcnkoY2FyZXQpICMgZm9yIGR1bW15VmFycyBmdW5jdGlvbg0KbGlicmFyeShwc3ljaCkgIyBmb3IgcGFpcnMucGFuZWxzIGZ1bmN0aW9uDQpsaWJyYXJ5KG1lbWlzYykgICMgY29tcGFyZSBtb2RlbHMNCg0KbGlicmFyeShkcmMpICAjICJkcmMiID0gZG9zZSByZXNwb25zZSBjdXJ2ZXMgKGNvbnRhaW5zIGEgc3VpdGUgb2YgZmxleGlibGUgYW5kIHZlcnNhdGlsZSBtb2RlbCBmaXR0aW5nIGFuZCBhZnRlci1maXR0aW5nIGZ1bmN0aW9ucykNCmxpYnJhcnkocGxvdGx5KQ0KDQpgYGANCg0KDQoNCj4gZmxhc2ggZm9yd2FyZCEgc2Nyb2xsIHRvIHRoZSBib3R0b20gYW5kIHJ1biBiaWdjaHVua190aWR5dHJhbnNmb3JtDQoNCg0KI2ltcG9ydCBkYXRhDQpgYGB7cn0NCnJtKGxpc3QgPSBscygpKSAjIGNsZWFyIHdvcmtzcGFjZQ0KbXlkYXRhIDwtcmVhZC5jc3YoInVqYmNfYV8xNDY5MDgxX3NtNTQ5Ni50eHQiLHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgICMjIFNQRUNJRlkgZmlsZW5hbWUNCg0KbXlkYXRhICU+JSBmaWx0ZXIoZXhwdCA9PSIgMkEiKSAlPiUgZHBseXI6OnNlbGVjdChzYW1wbGVfaWQpDQoNCiNkcHV0KG5hbWVzKG15ZGF0YSkpDQoNCmBgYA0KDQoNCg0KIyBtYW51YWwgYWRkIHRpbWUgemVybyB0byAiZGF5cyBvbiBob3BzIiBleHBlcmltZW50IQ0KQW50b24gUGFhciBkYXRhIGF0IHRpbWUgemVybyBmb3Iga2luZXRpY3MgZXhwZXJpbWVudCAodHJpcGxpY2F0ZSBzYW1wbGVzIHJ1biBpbW1lZGlhdGVseSBhZnRlciBzYW1wbGUgY29sbGVjdGlvbiwgd2hpbGUgdGhlIHJlbWFpbmRlciB3ZXJlIGJlaW5nIGRyeS1ob3BwZWQpOg0KRGF0ZQl0aW1lCXNhbXBsZSBuby4JQUJWCUFCVwlPRSAoUCkJRXIJRWEJU0cgMjAvMjAJUkRGCUFERiBzYW1wbGVfaWQNCjA3LzI3LzE3CTM6MjYgUE0JMDNfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43NAkxLjAxNDYyCTYzLjg4CTc2LjU4CUpLMS0yMC00MQ0KMDcvMjcvMTcJMzozMCBQTQkwNF8JNi42OAk1LjIJMTUuOTUJNi4wOAkzLjczCTEuMDE0NjEJNjMuODkJNzYuNglKSzEtMjAtNTANCjA3LzI3LzE3CTM6MzQgUE0JMDVfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43NAkxLjAxNDYyCTYzLjg4CTc2LjU4CUpLMS0yMC04Nw0KDQpgYGB7cn0NCiMjIGZpbGwgaW4gTUlTU0lORyBEQVRBIDooICBhZGQgdGltZSB6ZXJvIGRhdGEgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzKQ0KDQojIyBuZXN0ZWQgaWZlbHNlIHRvIGFkZCB0aW1lIHplcm8gcGxhdG8gdmFsdWVzIGZvciBlYWNoIGV4cHQ6DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZSguLCBpbml0aWFsX3BsYXRvID0gaWZlbHNlKGV4cHQ9PSIgMkEiLCAzLjc0LCBpZmVsc2UoZXhwdD09IiAxQSIsIDMuNjksIGlmZWxzZShleHB0PT0iIDFCIiwgMy41MywgaWZlbHNlKGV4cHQ9PSIgM0IiLCAzLjU0LCBpZmVsc2UoZXhwdD09IiA0QiIsIDQuNjYsIE5BKSkpKSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiMjIGFkZCBjb21wbGV0ZSB0aW1lLXplcm8gYW50b24gcGFhciBkYXRhIGZvciB0aW1lIHNlcmllcyAoZXhwdDJBKQ0KIyMgcXVhZHJ1cGxlLXVwIGVhY2ggdHJpcGxpY2F0ZSB0aW1lLXplcm8gbWVhc3VyZW1lbnQgKG9uZSBjb3B5IGZvciBlYWNoIE5IL0RIICYgcm91c2Uvc3RpbGwgY29tYm8pDQojIyBub3RlOiAgdGhpcyBwcmFjdGljZSBpcyBvZiBxdWVzdGlvbmFibGUgc3RhdGlzdGljYWwgcmlnb3IuICBJbiByZXRyb3NwZWN0IGl0IHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgKGFuZCBhIGxvdCBlYXNpZXIpIHRvIGp1c3QgcnVuIDEyIHNhbXBsZXMgdGhyb3VnaCBBbnRvbiBvbiBkYXkwIQ0KdGltZXplcm8gPC0gbXlkYXRhICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBhcnJhbmdlKHNwZWNpYWxfZ3JvdXApICU+JSBzbGljZSgxOjEyKQ0KI2RhdGEuZW50cnkodGltZXplcm8pDQp0aW1lemVybyRUZXN0X0RhdGUgPC0gcmVwKGMoIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIpLDQpDQp0aW1lemVybyRUZXN0LnRpbWUgPC0gcmVwKGMoIjM6MjYgUE0iLCAiMzozMCBQTSIsICIzOjM0IFBNIiksNCkNCnRpbWV6ZXJvJEFCViA8LSA2LjY4DQp0aW1lemVybyRBQlcgPC0gNS4yDQp0aW1lemVybyRPRSA8LSAxNS45NQ0KdGltZXplcm8kRXIgPC0gNi4wOA0KdGltZXplcm8kRWEgPC0gcmVwKGMoMy43NCwgMy43MywgMy43NCksNCkNCnRpbWV6ZXJvJFNHIDwtIHJlcChjKDEuMDE0NjIsIDEuMDE0NjEsIDEuMDE0NjIpLDQpDQp0aW1lemVybyRSREYgPC0gcmVwKGMoNjMuODgsIDYzLjg5LCA2My44OCksNCkNCnRpbWV6ZXJvJEFERiA8LSByZXAoYyg3Ni41OCwgNzYuNjAsIDc2LjU4KSw0KQ0KdGltZXplcm8kQ2Fsb3JpZXMgPC0gcmVwKGMoMjE1LjM3LDIxNS4zNCwyMTUuMzUpLDQpDQp0aW1lemVyb1t0aW1lemVybyRob3A9PSJESCIsXSRjb250YWN0X2RheXMgPC0wDQp0aW1lemVyb1ssYygyODozMSldIDwtIDANCm15ZGF0YSA8LSByYmluZCh0aW1lemVybyxteWRhdGEpDQp3cml0ZS5jc3YobXlkYXRhLCAicXVpY2tpZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm15ZGF0YTwtcmVhZC5jc3YoInF1aWNraWUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojIyMgIHRoaXMgaXMgYSByb3V0aW5lIGZvciBpbnNwZWN0aW5nIGRhdGEgIyMjDQpteWRhdGF0eXBlczwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2FwcGx5KG15ZGF0YSwgY2xhc3MpKSkgICMjIHRhYmxlIG9mIGRhdGF0eXBlcw0KbXlkYXRhdHlwZXMkaGVhZGVycyA8LSByb3duYW1lcyhteWRhdGF0eXBlcykgICAgIyMgY29udmVydCByb3duYW1lcyB0byB2YWx1ZXMNCm5hX2NvdW50IDwtYXMuZGF0YS5mcmFtZShzYXBwbHkobXlkYXRhLCBmdW5jdGlvbih5KSBzdW0obGVuZ3RoKHdoaWNoKGlzLm5hKHkpKSkpKSkgIyMgY291bnQgTkFzIGJ5IGNvbHVtbg0KbXlkYXRhT3ZlcnZpZXc8LWFzLmRhdGEuZnJhbWUoY2JpbmQobmFfY291bnQsbXlkYXRhdHlwZXMpKSAgIyMgdGFibGUgb2YgTkEgY291bnRzIGFuZCBkYXRhdHlwZXMNCm5hbWVzKG15ZGF0YU92ZXJ2aWV3KSA8LSBjKCJOQV9jb3VudCIsIkRhdGFfQ2xhc3MiLCAiSGVhZGVyIikNCnN1bShpcy5uYShteWRhdGEpKSAgIyMgdG90YWwgY291bnQgb2YgTkEgdmFsdWVzIGluIGVudGlyZSBzaGVldCAoMjMxIGluIHRoaXMgY2FzZSkNCm15ZGF0YU92ZXJ2aWV3ICU+JSBmaWx0ZXIoTkFfY291bnQ+MCkgICAgIyMgYnJlYWtkb3duIG9mIE5BIHZhbHVlcw0KYGBgDQoNCg0KIyB0aWR5ICYgdHJhbnNmb3JtIA0KDQo+IG5vdGU6IHRoaXMgaXMgYSBiYXNlIFIgK2RwbHlyIGRhdGEgd3JhbmdsaW5nIGV4ZXJjaXNlISBpbiBnZW5lcmFsIGl0J3MgYmVzdCB0byB1c2UgbHVicmlkYXRlIHBhY2thZ2UgZm9yIGRhdGUvdGltZXN0YW1wcyEgDQoNCiogY3JlYXRlIGRhdGUgY29sdW1uIChvbmUgYXQgYSB0aW1lKQ0KICAgICogaGVyZSB3ZSdyZSBjcmVhdGluZyBuZXcgY29sdW1ucyBpbiBQT1NJWGN0IGZvcm1hdCwgYmFzZWQgb24gdGhlIHBhcnRpY3VsYXIgZGF0ZSBmb3JtYXQgdXNlZCBpbiB0aGUgc291cmNlIGRhdGEgKHVqYmNfYV8xNDY5MDgxX3NtNTQ5Ni50eHQ7IHNlZSA/YXMuUE9TSVhjdCk6DQpgYGB7cn0NCnRlc3RfZGY8LSBteWRhdGEgIyAob3JpZ2luYWwgZGF0YWZyYW1lIHRvIGJlIHVzZWQgZm9yIHRlc3RpbmcvaWxsdXN0cmF0aW9uKQ0KDQp4X2JyZXdfZGF0ZS5QT1NJWGN0PC0gYXMuUE9TSVhjdChteWRhdGEkYnJld19kYXRlLCBmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIGFzIGEgc3RhbmRhbG9uZSB2ZWN0b3IgKFVTRUxFU1MgaGVyZSksIG9yOg0KdGVzdF9kZiRicmV3X2RhdGUuUE9TSVhjdCA8LWFzLlBPU0lYY3QobXlkYXRhJGJyZXdfZGF0ZSwgZm9ybWF0ID0gIiVtLyVkLyVZIikgICMgYXMgYSAibmV3IGNvbHVtbiIgaW4gb3VyIGRhdGFmcmFtZQ0KYGBgDQoNCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogDQogICAgKiBjcmVhdGUgbXVsdGlwbGUgZGF0ZSBjb2x1bW5zIHVzaW5nIEZPUkxPT1ANCihoZXJlIHdlIHRha2UgYWR2YW50YWdlIG9mIHRoZSBwYXR0ZXJuIHRoYXQgb2Ygb3VyIChjaGFyYWN0ZXIpIGRhdGUvdGltZSBjb2x1bW5zIGhhdmUgdGhlIHN0cmluZyAiZGF0ZSIgaW4gdGhlIGhlYWRlcm5hbWUuICBGaXJzdCBtYWtlIGNoYXJhY3RlciB2ZWN0b3Igb2YgYWxsIGNvbHVtbiBuYW1lcyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIsIHRoZW4gcnVuIEZPUkxPT1Agb3ZlciBlYWNoIGRhdGUgY29sdW1uKS4gIFRoaXMgZm9ybG9vcCB3aWxsIGNvbnZlcnQgZGF0ZWNvbHMgaW50byBQT1NJWGN0IGZvcm1hdCAoUi91bml2ZXJzYWwgZGF0ZXRpbWUgZm9ybWF0KS4gIFRoZXkgc2F5IGl0J3MgICoqYmVzdCB0byBhdm9pZCBmb3Jsb29wcyoqIGluIGdlbmVyYWwsIGFuZCAqKmluIHRoaXMgY2FzZSB5b3Ugd291bGQgcHJvYmFibHkgdXNlIGZ1bmN0aW9ucyBmcm9tIGx1YnJpZGF0ZSBwYWNrYWdlKiogLSBhbmQgaW4gZ2VuZXJhbCBiZSBhd2FyZSB0aGVyZSdzIHByb2JhYmx5IGEgc3BlY2lmaWMgdG9vbCBmb3IgdGhlIHRhc2sgYXQgaGFuZCkhIQ0KYGBge3J9DQpkYXRlY29sczwtIGRwdXQobmFtZXMoZHBseXI6OnNlbGVjdChteWRhdGEsIG1hdGNoZXMoImRhdGUiKSkpKSAgIyMgaGVhZGVycyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIgPT4gYygiYnJld19kYXRlIiwgInNhbXBsZV9jb2xsZWN0aW9uX2RhdGUiLCAiZHJ5aG9wX2RhdGUiLCAiVGVzdF9EYXRlIikNCiMjIEZPUkxPT1ANCmZvciAoaWNvbCBpbiBkYXRlY29scykgew0KICBuZXdjb2wgPSBwYXN0ZTAoaWNvbCwiLlBPU0lYY3QiKQ0KICMgcHJpbnQobmV3Y29sKQ0KICBteWRhdGFbLCBuZXdjb2xdID0gYXMuUE9TSVhjdChteWRhdGFbLCBpY29sXSxmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIyMgIENSRUFURSBORVcgY29sdW1ucyB3aXRoIFBPU0lYY3QgICANCiMgIG15ZGF0YVssIG5ld2NvbF0gPSBhcy5QT1NJWGN0KGFzLm51bWVyaWMobXlkYXRhWywgaWNvbF0pICAqICg2MCo2MCoyNCksIG9yaWdpbj0iMTg5OS0xMi0zMCIpICMjIyAgbWljcm9zb2Z0IHRpbWVzDQp9DQpgYGANCg0KDQoqIGNyZWF0ZSBkYXlvZmFkZGl0aW9uLCBkYXlzb25ob3BzLCBob3BzX2dfMTAwbUwsIHBvdW5kc19iYmwgdmFyaWFibGVzIHdpdGggZHBseXIgIm11dGF0ZSINCmBgYHtyfQ0KbXlkYXRhPC0gbXlkYXRhICU+JSANCiAgbXV0YXRlKGRheW9mYWRkaXRpb24gPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoZHJ5aG9wX2RhdGUuUE9TSVhjdCxicmV3X2RhdGUuUE9TSVhjdCkpKSwNCiAgICAgICAgIGRheXNvbmhvcHMgPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoVGVzdF9EYXRlLlBPU0lYY3QsZHJ5aG9wX2RhdGUuUE9TSVhjdCkpLyg2MCo2MCoyNCkpLA0KICAgICAgICAgaG9wc19nXzEwMG1MID0gKG1nX2hvcHMvdm9sdW1lX21MKS8xMCwNCiAgICAgICAgIHBvdW5kc19iYmwgPSAobWdfaG9wcy92b2x1bWVfbUwpKjExNy80NTQNCiAgICAgICAgICkNCmBgYA0KDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IG1hbnVhbGx5IHVucGFjayBvdmVybG9hZGVkIGNvbHVtbnMgd2l0aCBncmVwbA0KYGBge3J9DQpteWRhdGEkT1g8LWdyZXBsKCJPWCIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsICJPWCIgY29sdW1uDQpteWRhdGEkcm91c2U8LWdyZXBsKCJyb3VzZSIsIG15ZGF0YSRzcGVjaWFsX2dyb3VwKSMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRHcmluZDwtZ3JlcGwoIkdyaW5kIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJENvbmU8LWdyZXBsKCJDb25lIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTQ8LWdyZXBsKCIxNCIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNTwtZ3JlcGwoIjE1IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE3PC1ncmVwbCgiMTciLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpgYGANCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogaWZlbHNlIHN0YXRlbWVudHMgZm9yIGhhcnZlc3R5ZWFyLCBmb3JtX29mX2hvcHMsIGFuZCBESF90ZW1wDQpgYGB7cn0NCiMjIGlmZWxzZSBzdGF0ZW1lbnQgZm9yIGhhcnZlc3R5ZWFyIChpZiBuZWl0aGVyIDIwMTQgbm9yIDIwMTUgbm9yIDIwMTcsIHRoZW4gMjAxNikNCm15ZGF0YSRoYXJ2ZXN0WWVhciA8LSBpZmVsc2UoDQogIG15ZGF0YSRoYXJ2ZXN0MjAxND09VFJVRSwgMjAxNCwNCiAgaWZlbHNlKG15ZGF0YSRoYXJ2ZXN0MjAxNT09VFJVRSwgMjAxNSwgDQogICAgICAgICBpZmVsc2UobXlkYXRhJGhhcnZlc3QyMDE3PT1UUlVFLCAyMDE3LCAyMDE2KSkpDQpkcHV0KGxldmVscyhhcy5mYWN0b3IobXlkYXRhJGhhcnZlc3RZZWFyKSkpDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciBmb3JtIG9mIGhvcHMgKGlmIG5laXRoZXIgY29uZSBub3IgZ3JvdW5kIG5vciBOSCwgdGhlbiBwZWxsZXQpIA0KbXlkYXRhJGZvcm1fb2ZfaG9wcyA8LSBpZmVsc2UoDQogIG15ZGF0YSRDb25lPT1UUlVFLCAiY29uZSIsDQogIGlmZWxzZShteWRhdGEkR3JpbmQ9PVRSVUUsICJncm91bmQiLA0KICAgICAgICAgaWZlbHNlKG15ZGF0YSRIb3BfdHlwZT09Ik5IIiwgIk5IIiwgInBlbGxldCIpKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkZm9ybV9vZl9ob3BzKSkpDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciB0ZW1wZXJhdHVyZSBncmVhdGVyIG9yIGxlc3MgdGhhbiAxMCANCm15ZGF0YSRESF90ZW1wIDwtIGlmZWxzZSgNCiAgbXlkYXRhJHRlbXAuQzwxMCwgImNvbGQiLA0KICBpZmVsc2UobXlkYXRhJHRlbXAuQz4xMCwgIndhcm0iLCAic29tZXRoaW5nIGVsc2UiKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkREhfdGVtcCkpKQ0KYGBgDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IGNyZWF0ZSAidmFyaWV0eSIgY29sdW1uIHN0YXJ0aW5nIHdpdGggb3ZlcmxvYWRlZCAiSG9wX3R5cGUiIGNvbHVtbiwgdGhlbiBzdHJpcHBpbmcgYXdheSBhbGwgdGhlIG5vbi12YXJpZXR5IGluZm9ybWF0aW9uDQpgYGB7cn0NCm15ZGF0YSR2YXJpZXR5PC1teWRhdGEkSG9wX3R5cGUNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiWzAtOV0rIiwiIiwgbXlkYXRhJHZhcmlldHkpICMjIHJlbW92ZSBhbGwgbnVtYmVycw0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCJPWCIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgIyMgcmVtb3ZlIHNwZWNpZmljIHRleHQNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiR3JpbmQiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiQ29uZSIsIiIsIG15ZGF0YSR2YXJpZXR5KQ0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJHZhcmlldHkpICAgICAgIyMgcmVtb3ZlIHNwYWNlcw0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSR2YXJpZXR5KSkpDQpgYGANCg0KKiBBRFZBTkNFRCBkYXRhIHdyYW5nbGluZzogY3JlYXRlICJFWFBUbmV3IiAoZWFjaCB1bmlxdWUgZXhwZXJpbWVudGFsIGdyb3VwIGRlc2lnbmF0ZWQgaW4gYSBzaW5nbGUgdmFyaWFibGUpDQpgYGB7cn0NCm15ZGF0YTwtIG15ZGF0YSAlPiUgbXV0YXRlKEVYUFRuZXc9cGFzdGUwKCJncm91cCIsZXhwdCwgc3Vic3RyKHNwZWNpYWxfZ3JvdXAsIDEsMiksYXMuY2hhcmFjdGVyKHJvdXNlKSkpDQojIGNsZWFuIGl0IHVwIGJ5IHJlbW92aW5nICJOQSIgYW5kIGFueSBzcGFjZXMgZHVlIHRvIGNhbmFyeWNvZGUgYnVnDQpteWRhdGEkRVhQVG5ldyA8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJEVYUFRuZXcpICAjIyByZW1vdmUgYW55IHNwYWNlcw0KbXlkYXRhJEVYUFRuZXcgPC0gZ3N1YigiTkEiLCIiLCBteWRhdGEkRVhQVG5ldykgIyMgcmVtb3ZlICJOQSINCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkZXhwdCkpKQ0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSRFWFBUbmV3KSkpDQpgYGANCg0KDQojIyBzZWxlY3QgYW5kIHJlYXJyYW5nZSBjb2x1bW5zIA0KKGV4cGVyaW1lbnRhbCBmYWN0b3JzIG9uIHRoZSBsZWZ0LCB0aGVuIG1lYXN1cmVtZW50cywgZm9sbG93ZWQgYnkgY2FsY3VsYXRpb25zIGFuZCB0aGVuIGFsbCBkYXRlIGNvbHVtbnMgb24gdGhlIHJpZ2h0KToNCmBgYHtyfQ0KbXlkYXRhIDwtIG15ZGF0YSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLGV4cHQsIEVYUFRuZXcsIGhvcCwgQklOaG9wLCB2YXJpZXR5LCBPWCwgaGFydmVzdFllYXIsIGZvcm1fb2ZfaG9wcyxzcGVjaWFsX2NvbmRpdGlvbnMsIHJvdXNlLCBkYXlzb25ob3BzLCBkYXlvZmFkZGl0aW9uLCBESF90ZW1wLCB0ZW1wLkMsIGhvcHNfZ18xMDBtTCwgcG91bmRzX2JibCwNCiAgICAgICAgIEFCViwgQUJXLCBPRSwgRXIsIEVhLCBTRywgUkRGLCBBREYsIENhbG9yaWVzLCANCiAgICAgICAgIGRob3BfZGF5LCBjb250YWN0X2RheXMsIFJFRl9OSCwgQUJWX2luY3JlYXNlLCANCiAgICAgICAgIGJyZXdfZGF0ZS5QT1NJWGN0LCBzYW1wbGVfY29sbGVjdGlvbl9kYXRlLlBPU0lYY3QsIGRyeWhvcF9kYXRlLlBPU0lYY3QsVGVzdF9EYXRlLlBPU0lYY3QsIGluaXRpYWxfcGxhdG8pDQojIyByZW1vdmUgIi5QT1NJWGN0IiBzdWZmaXguICBMZWF2aW5nIGl0IGFzLWlzIHdpbGwgb25seSBhZGQgdG8gY29uZnVzaW9uIGlmL3doZW4gdGhlc2UgZGF0YSBhcmUgc2F2ZWQgYW5kIHJlLWltcG9ydGVkIChhbmQgYmVjb21lICdjaGFyYWN0ZXInIGZvcm1hdCEpDQpjb2xuYW1lcyhteWRhdGEpID0gZ3N1YigiLlBPU0lYY3QiLCAiIiwgY29sbmFtZXMobXlkYXRhKSkNCmBgYA0KDQoNCg0KIyNjb21wdXRlIG1lYW4gTkggKGNvbnRyb2wpIHZhbHVlcyANCihOSCA9IG5vdCBkcnktaG9wcGVkKSANCmBgYHtyfQ0KIyMgbWVhbiBOSCAoY29udHJvbCkgdmFsdWVzIGZvciBlYWNoIEVYUFRuZXcgZ3JvdXANCm1lYW4uY29udHJvbF9OSDwtIG15ZGF0YSAlPiUgDQogIGdyb3VwX2J5KEVYUFRuZXcpICU+JQ0KICBmaWx0ZXIoaG9wPT0iTkgiKSAlPiUNCiAgc3VtbWFyaXNlX2F0KHZhcnMoQUJWLCBBQlcsIEVhLCBTRyksZnVucyhtZWFuLCBuKCkpKQ0KIyMgcmVwbGFjZSAiX21lYW4iIHdpdGggIi5jb250cm9sX05IIiBpbiBjb2x1bW4gbmFtZXMNCmNvbG5hbWVzKG1lYW4uY29udHJvbF9OSCkgPSBnc3ViKCJfbWVhbiIsICIuY29udHJvbF9OSCIsIGNvbG5hbWVzKG1lYW4uY29udHJvbF9OSCkpDQpgYGANCg0KDQojI2NvbXB1dGUgJFxEZWx0YSQgdmFsdWVzIChjaGFuZ2VzIHJlbGF0aXZlIHRvIHVuLWRyeWhvcHBlZCBjb250cm9scykgdXNpbmcgb2JqZWN0cyBjcmVhdGVkIGFib3ZlLi4uDQoqIGRpZmZlcmVuY2Ugb2YgZWFjaCBBQlYsQUJXLEVhLCBhbmQgU0cgZGF0YSBwb2ludCBmcm9tIGNvcnJlc3BvbmRpbmcgbWVhbi5jb250cm9sX05IIHZhbHVlcyAodW5ob3BwZWQgc2FtcGxlcyBpbiBzYW1lIEVYUFRuZXcgZ3JvdXApDQpgYGB7cn0NCiMjIGZpcnN0IGpvaW4gb3VyIGRhdGEgd2l0aCBtZWFuIEFCViBmb3IgdW5ob3BwZWQgc2FtcGxlcyBpbiBnaXZlbiBleHBlcmltZW50IChtZWFuLmNvbnRyb2xfTkg7IGNhbGN1bGF0ZWQgYWJvdmUpDQoNCkZQSGNhbGM8LSBsZWZ0X2pvaW4obXlkYXRhLCBtZWFuLmNvbnRyb2xfTkgsIGJ5PSJFWFBUbmV3IikNCg0KIyMgY2FsY3VsYXRlIEFCV19pbmNyZWFzZSBieSBzdWJ0cmFjdGluZyBlYWNoIGluZGl2aWR1YWwgQUJXIG1lYXN1cmVtZW50IGZyb20gcmVzcGVjdGl2ZSBtZWFuLmNvbnRyb2xfTkg6DQpGUEhjYWxjJGRlbHRhLkFCViA8LSBGUEhjYWxjJEFCViAtIEZQSGNhbGMkQUJWLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEuQUJXIDwtIEZQSGNhbGMkQUJXIC0gRlBIY2FsYyRBQlcuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5wbGF0byA8LSBGUEhjYWxjJEVhIC0gRlBIY2FsYyRFYS5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLlNHIDwtIEZQSGNhbGMkU0cgLSBGUEhjYWxjJFNHLmNvbnRyb2xfTkgNCg0KIyMgdGhlIGNvbnRyb2wgc2FtcGxlcyBoYXZlIHNlcnZlZCB0aGVpciBwdXJwb3NlLCBub3cgcmVtb3ZlIHRoZW0gZnJvbSBkYXRhc2V0LiBUaGUgZm9sbG93aW5nIGNhbGN1bGF0aW9ucyBhcmUgb25seSBtZWFuaW5nZnVsIGZvciBkcnktaG9wcGVkIHNhbXBsZXMuICANCkZQSGNhbGM8LSBGUEhjYWxjICU+JSBmaWx0ZXIoaG9wPT0iREgiKQ0KYGBgDQoNCg0KIyMgKmNhbGN1bGF0ZSogY29ycmVzcG9uZGluZyBDTzIgcHJvZHVjdGlvbiANCiogZm9sbG93aW5nIEJhbWZvcnRoIChkZXNjcmliaW5nIEJhbGxpbmcgZXF1YXRpb24pICIuLi5tb3JlIHJlYWxpc3RpY2FsbHksIHRoZSBldGhhbm9sIHlpZWxkIGlzIG1vcmUgbGlrZSAwLjQ2IGcgYW5kIGNhcmJvbiBkaW94aWRlIDAuNDQgZyBmcm9tIDEgZyBzdWdhciIgIChwLiAxMzcgaW4gQnJld2luZyBNYXRlcmlhbHMgYW5kIFByb2Nlc3NlczogQSBQcmFjdGljYWwgQXBwcm9hY2ggdG8gQmVlciBFeGNlbGxlbmNlLCBFZGl0ZWQgYnkgQ2hhcmxlcyBCYW1mb3J0aCBBY2FkZW1pYyBQcmVzcywgMjAxNikNCmBgYHtyfQ0KRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlIDwtIEZQSGNhbGMkZGVsdGEuQUJXKigwLjQ0LzAuNDYpDQojI2NvbnZlcnQgY2FsY0NPMl9pbmNyZWFzZSAoaW4gZy8xMDBtTCkgdG8gY2FsY3VsYXRlZCBDTzIgdm9sdW1lcyBhZGRlZA0KIyMgZy9MID0gMTAqIGcvMTAwbUwNCiMjIFRoZSBjb252ZXJzaW9uIGZhY3RvciBmcm9tIHZvbHVtZXMgb2YgQ08yIHRvIENPMiBieSB3ZWlnaHQgKGcvTCkgaXMgMS45Ni4gRm9yIGV4YW1wbGU6IDIuNSB2b2x1bWVzIHggMS45NiA9IDQuOSBnL2wuDQpGUEhjYWxjJGNhbGNDTzJ2b2xzX2luY3JlYXNlIDwtIEZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSoxMC8xLjk2DQpgYGANCg0KDQoNCiMjIGRlZmluZSAiRlBIIiBhcyBhbW91bnQgcHJvZHVjZWQgcGVyIGFtb3VudCBkcnktaG9wcyBhZGRlZCAoYWxsIGluIGcvMTAwbUwpOg0KYGBge3J9DQojIyAgRlBIID0gRm9sZCBQcm9kdWN0aW9uIGR1ZSB0byBIb3BzIChmb2xkLWluY3JlYXNlIGJ5IG1hc3M6IGFtb3VudCBvZiBnaXZlbiBlbmRwb2ludCByZWxhdGl2ZSB0byBhbW91bnQgb2YgaG9wcyBhZGRlZCkNCiMjICANCkZQSGNhbGMkRlBIX0V0T0ggPSBGUEhjYWxjJGRlbHRhLkFCVy9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KRlBIY2FsYyRGUEhfQ08yID0gRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQpGUEhjYWxjJEZQSF9wbGF0byA9IEZQSGNhbGMkZGVsdGEucGxhdG8vRlBIY2FsYyRob3BzX2dfMTAwbUwNCg0KIyMgcmVwbGFjaW5nIHRpbWUtemVybyB0aW1lIHZhbHVlcyB3aXRoIGEgdmVyeSBzbWFsbCBudW1iZXIgKHJhdGhlciB0aGFuIGV4YWN0bHkgemVybykgd2lsbCBwcmV2ZW50IGlzc3VlcyB3aXRoIGFuYWx5c2lzIG9mIG5vbmxpbmVhciBtb2RlbHMNCkZQSGNhbGNbRlBIY2FsYyRkYXlzb25ob3BzPT0wLF0kZGF5c29uaG9wcyA8LSAwLjAxDQoNCg0KDQojIyBhbmQgc2F2ZSB0aGUgdHJhbnNmb3JtZWQgZGF0YSB0byBjc3Y6DQp3cml0ZS5jc3YoRlBIY2FsYywiRlBIY2FsYy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCkZQSGNhbGMgPC0gcmVhZC5jc3YoIkZQSGNhbGMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUpDQoNCmRmPC0gRlBIY2FsYyAlPiUgZ3JvdXBfYnkoRVhQVG5ldykgJT4lDQogIHN1bW1hcmlzZV9hdCh2YXJzKGhvcHNfZ18xMDBtTCxwb3VuZHNfYmJsLGRlbHRhLkFCViwgZGVsdGEuQUJXLCBkZWx0YS5wbGF0bywgRlBIX3BsYXRvLCBGUEhfRXRPSCwgRlBIX0NPMiwgY2FsY0NPMnZvbHNfaW5jcmVhc2UpLGZ1bnMocm91bmQobWVhbiguKSwgMikpKSAlPiUNCiAgYXJyYW5nZShkZXNjKEZQSF9FdE9IKSkgDQpkZg0KYGBgDQoNCg0KDQoNCg0KDQojIGludGVybWlzc2lvbj8NCg0KDQoNCg0KDQoNCiMgdmlzdWFsaXplDQojIyB2ZWN0b3J+dmVjdG9yKnZlY3RvciBwbG90cw0KYGBge3IgcmVncmVzc2lvbi4yfQ0KZGYgPC1GUEhjYWxjDQpwMTwtIGdncGxvdChkZiwgYWVzKHk9RlBIX0V0T0gseD1PRSwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAyPC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUFERiwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAzPC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUFCVywgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnA0PC0gZ2dwbG90KGRmLCBhZXMoeT1GUEhfRXRPSCx4PUVhLCBjb2xvcj1jb250YWN0X2RheXMpKSArIGdlb21fcG9pbnQoc2l6ZT0yKQ0KZ3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBuY29sID0gMikNCmBgYA0KDQoNCg0KIyMgeH55Kig0IHZlY3RvcnMpIGJ5IGNvbG9yDQpgYGB7ciByZWdyZXNzaW9uLjRwbG90LmNvbG9yfQ0KZGYgPC0gRlBIY2FsYw0KeDwtIGRmJEVhDQp5PC0gZGYkQUJXICAgICNGUEhfRXRPSA0KDQpwMTwtIGdncGxvdChkZiwgYWVzKHgseSwgY29sb3I9Zm9ybV9vZl9ob3BzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAyPC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1icmV3X2RhdGUpKSArIGdlb21fcG9pbnQoc2l6ZT0yKQ0KcDM8LSBnZ3Bsb3QoZGYsIGFlcyh4LHksIGNvbG9yPXJvdXNlKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnA0PC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1wb3VuZHNfYmJsKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbmNvbCA9IDIpDQpgYGANCg0KIyMgM0QgcGxvdHMgd2l0aCBsaWJyYXJ5KHBsb3RseSkNCmBgYHtyfQ0KIyBpbnRlcmFjdGl2ZSAzRCBwbG90cyB3aXRoIGxpYnJhcnkocGxvdGx5KQ0KcGxvdF9seShkYXRhID0gZGYsIHogPSBkZiRQTEFUTy5ob3Bkcm9wLCB4ID0gfmRmJGRheXNvbmhvcHMsIHkgPSAgZGYkQUJXKQ0KYGBgDQoNCiMjIEFtdW5hdGVndWkgIlVzaW5nIENvcnJlbGF0aW9ucyBUbyBVbmRlcnN0YW5kIFlvdXIgRGF0YSINCmBgYHtyIEFtdW5hdGVndWl9DQpteWRhdGE8LUZQSGNhbGMgJT4lIGRwbHlyOjpzZWxlY3QoZXhwdCx2YXJpZXR5LGZvcm1fb2ZfaG9wcyxwb3VuZHNfYmJsLHNwZWNpYWxfY29uZGl0aW9ucyxyb3VzZSxkYXlzb25ob3BzLERIX3RlbXAsYnJld19kYXRlLEFCVyxTRyxPRSxBREYsUkRGLGNhbGNDTzJ2b2xzX2luY3JlYXNlLEZQSF9DTzIsIEZQSF9FdE9IKSAgIyMgbGFzdCBvbmUgaXMgMTAwJSBpbiBqDQoNCiMjIG5vdGUgdXNlIG9mICJkcGx5cjo6c2VsZWN0IiBiZWNhdXNlIG9uZSBvZiB0aGVzZSBwYWNrYWdlcyBpcyBjb25mbGljdGluZyB3aXRoIGRwbHlyIGNvbW1hbmRzIDooKQ0KDQojIyBmb2xsb3dpbmcgIE1hbnVlbCBBbXVuYXRlZ3VpICBodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWlnUFEtcEk4QmpvDQojIyBVc2luZyBDb3JyZWxhdGlvbnMgVG8gVW5kZXJzdGFuZCBZb3VyIERhdGE6IE1hY2hpbmUgTGVhcm5pbmcgV2l0aCBSIA0KIyNmdW5jdGlvbnMgZm9yIGZsYXR0ZW5TcXVhcmVNYXRyaXgNCmNvci5wcm9iIDwtIGZ1bmN0aW9uIChYLCBkZnI9bnJvdyhYKSAtMikgew0KICBSPC0gY29yKFgsIHVzZT0icGFpcndpc2UuY29tcGxldGUub2JzIikNCiAgYWJvdmU8LSByb3coUikgPCBjb2woUikNCiAgcjIgPC0gUlthYm92ZV1eMg0KICBGc3RhdDwtIHIyICogZGZyLygxLXIyKQ0KICBSW2Fib3ZlXSA8LSAxLSBwZihGc3RhdCwgMSwgZGZyKQ0KICBSW3JvdyhSKSA9PSBjb2woUildIDwtIE5BDQogIFINCn0NCmZsYXR0ZW5TcXVhcmVNYXRyaXggPC0gZnVuY3Rpb24obSkgew0KICBpZiggKGNsYXNzKG0pICE9ICJtYXRyaXgiKSB8IChucm93KG0pIT1uY29sKG0pKSkgc3RvcCgiTXVzdCBiZSBhIHNxdWFyZSBtYXRyaXguIikNCiAgaWYoIWlkZW50aWNhbChyb3duYW1lcyhtKSwgY29sbmFtZXMobSkpKSBzdG9wKCJSb3cgYW5kIGNvbHVtbiBuYW1lcyBtdXN0IGJlIGVxdWFsLiIpDQogIHV0IDwtIHVwcGVyLnRyaShtKQ0KICBkYXRhLmZyYW1lKGkgPSByb3duYW1lcyhtKVtyb3cobSlbdXRdXSwNCiAgICAgICAgICAgICBqID0gcm93bmFtZXMobSlbY29sKG0pW3V0XV0sDQogICAgICAgICAgICAgY29yPXQobSlbdXRdLA0KICAgICAgICAgICAgIHA9bVt1dF0pDQp9DQoNCiMjIGxpYnJhcnkoY2FyZXQpIHRvIGR1bW1pZnkgZXZlcnl0aGluZyAodHVybiBhbGwgY2hhcmFjdGVycyZmYWN0b3JzIGludG8gY29sdW1uczsgIGlnbm9yZXMgbnVtYmVycyBhbmQgaW50ZWdlcnMpDQpkbXk8LSBkdW1teVZhcnMoIiB+IC4iLGRhdGEgPSBteWRhdGEpDQpteWR1bW1pZmllZGRhdGE8LSBkYXRhLmZyYW1lKHByZWRpY3QoZG15LCBuZXdkYXRhID0gbXlkYXRhKSkNCmNvck1hdCA9IGNvcihteWR1bW1pZmllZGRhdGEpDQpjb3JNYXN0ZXJMaXN0PC0gZmxhdHRlblNxdWFyZU1hdHJpeChjb3IucHJvYihteWR1bW1pZmllZGRhdGEpKSAjIyBsaXN0IG9mIGFsbCBjb3JyZWxhdGlvbnMNCiMjIG9yZGVyIGJ5IHN0cmVuZ3RoIG9mIGNvcnJlbGF0aW9uDQpjb3JsaXN0PC0gY29yTWFzdGVyTGlzdCAlPiUgYXJyYW5nZSgtYWJzKGNvck1hc3Rlckxpc3QkY29yKSkgDQp3cml0ZS5jc3YoY29ybGlzdCxwYXN0ZTAoIkZMQVQgY29ycmVsYXRpb24gbWF0cml4Xy5jc3YiKSkNCmNvcmxpc3QgPC0gY29ybGlzdCAlPiUgZHBseXI6OmZpbHRlcihqPT0iRlBIX0V0T0giKSAgICMjIGZpbHRlciBzcGVjaWZpYyBlbmRwb2ludA0KaGVhZChjb3JsaXN0LDUwKQ0KYGBgDQoNCiMjIHBhaXJzLnBhbmVscyBjb3JyZWxhdGlvbiBtYXRyaXggZnJvbSBsaWJyYXJ5KHBzeWNoKQ0KYGBge3J9DQojIyBzcGVjaWZ5IGludGVyZXN0aW5nIHZhcmlhYmxlczogIA0KaW50ZXJlc3Rpbmd2YXJpYWJsZXM8LWMoIkFCVyIsICJwb3VuZHNfYmJsIiwgImRheXNvbmhvcHMiLCAiY2FsY0NPMnZvbHNfaW5jcmVhc2UiKSANCnBhaXJzLnBhbmVscyhteWR1bW1pZmllZGRhdGFbYyhpbnRlcmVzdGluZ3ZhcmlhYmxlcywgIkZQSF9FdE9IIildKQ0KYGBgDQoNCg0KI21vZGVsDQpPYnNlcnZpbmcgdGhlIGltcGFjdHMgb2YgZHJ5aG9wcGluZyBpbiB0aGUgcHJlc2VuY2Ugb2YgbGl2ZSB5ZWFzdCBoYXMgbGVkIG1hbnkgYnJld2luZyBwcm9mZXNzaW9uYWxzIHRvIHVuZGVyc3RhbmQgdGhhdCBGUEggaXMgYSBmdW5jdGlvbiBvZiBtYW55IG9mIHRoZSB2YXJpYWJsZXMgYWJvdmUgaW5jbHVkaW5nIGhvcCB2YXJpZXR5LGZvcm1fb2ZfaG9wcyxoYXJ2ZXN0WWVhcixPWCxESF90ZW1wLGRheXNvbmhvcHMscm91c2UscG91bmRzX2JibC4uLi4gTWFueSBoYXZlIGludHVpdGl2ZWx5IGNyZWF0ZWQgYSBtb2RlbCBpbiB0aGVpciBoZWFkcyAod2l0aG91dCBuZWNlc3NhcmlseSB0aGlua2luZyBvZiBpdCBhcyBzdWNoKSBhbmQgc2tpbGxmdWxseSBhZGp1c3QgcHJvY2VzcyB3aGVuIG5lY2Vzc2FyeSB0byBhY2NvdW50IGZvciB0aGlzIHBoZW5vbWVub24uICBJbiBsaW5lYXIgbW9kZWxpbmcsIG91ciBmdW5jdGlvbiB3aWxsIHRha2Ugb24gdGhlIGZvcm06DQokRlBIID0gIGludGVyY2VwdCArIFxiZXRhX3sxfVhfezF9ICsgXGJldGFfezJ9WF97Mn0gKyAuLi4gKyBcYmV0YV97bn1YX3tufSQgd2hlcmUgJFxiZXRhJCB2YWx1ZXMgYXJlIHdoYXQgd2UncmUgYXR0ZW1wdGluZyB0byBkZXJpdmUgaW4gdGhpcyBtb2RlbGluZyBleGVyY2lzZSwgYW5kIFggdmFsdWVzIGFyZSAoY29sbGVjdGl2ZWx5KSBhIHBhcnRpY3VsYXIgc2V0IG9mIGNvbmRpdGlvbnMuDQoNCj4+IEFjdGlvbkl0ZW06ICBhZGQgbGluayB0byBtb2RlbGluZyBvdmVydmlldw0KDQoNCiMjIGxpbmVhciBtb2RlbHMgMSAoY3JlYXRlIG1vZGVscykNCmBgYHtyfQ0KZGY8LUZQSGNhbGMNCg0KbG0xPC1sbShkZiRTR35kZiRPRSkNCmxtMjwtbG0oZGYkU0d+ZGYkQURGKQ0KbG0zPC1sbShkZiRTR35kZiRBQlcpDQpsbTQ8LWxtKGRmJFNHfmRmJEVhKQ0KcGFyKG1mcm93ID0gYygyLCAyKSwgb21hID0gYygwLCAwLCAwLCAwKSkNCnBsb3QoZGYkU0d+ZGYkT0UpDQphYmxpbmUobG0xKQ0KcGxvdChkZiRTR35kZiRBREYpDQphYmxpbmUobG0yKQ0KcGxvdChkZiRTR35kZiRBQlcpDQphYmxpbmUobG0zKQ0KcGxvdChkZiRTR35kZiRFYSkNCmFibGluZShsbTQpDQpgYGANCg0KIyMgbGluZWFyIG1vZGVscyAyICh2aWV3IHJlc2lkdWFsIHBsb3RzKQ0KYGBge3J9DQpkZjwtRlBIY2FsYw0KDQpsbTE8LWxtKGRmJFNHfmRmJE9FKQ0KbG0yPC1sbShkZiRTR35kZiRBREYpDQpsbTM8LWxtKGRmJFNHfmRmJEFCVykNCmxtNDwtbG0oZGYkU0d+ZGYkRWEpDQoNCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQpwbG90KGxtMSRyZXNpZHVhbHMpDQpwbG90KGxtMiRyZXNpZHVhbHMpDQpwbG90KGxtMyRyZXNpZHVhbHMpDQpwbG90KGxtNCRyZXNpZHVhbHMpDQojc3VtbWFyeShsbTQpDQpgYGANCg0KDQojIyBjb21wYXJlIGxpbmVhciBtb2RlbHMgdXNpbmcgbWVtaXNjIHBhY2thZ2UNCmBgYHtyfQ0KZGY8LUZQSGNhbGMNCg0KbG0xPC1sbShkZiRTR35kZiRPRSkNCmxtMjwtbG0oZGYkU0d+ZGYkQURGKQ0KbG0zPC1sbShkZiRTR35kZiRBQlcpDQpsbTQ8LWxtKGRmJFNHfmRmJEVhKQ0KDQptdGFibGUxMjM0IDwtIG10YWJsZSgiTW9kZWwgMSI9bG0xLCJNb2RlbCAyIj1sbTIsIk1vZGVsIDMiPWxtMywgIk1vZGVsIDQiPWxtNCwNCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyeS5zdGF0cz1jKCJzaWdtYSIsIlItc3F1YXJlZCIsIkYiLCJwIiwiTiIpLHNob3cuZXFuYW1lcz1UKQ0KDQptdGFibGUxMjM0YiA8LSByZWxhYmVsKG10YWJsZTEyMzQsDQogICAgICAgICAgICAgICAgICAgICAgIihJbnRlcmNlcHQpIiA9ICJDb25zdGFudCIsDQogICAgICAgICAgICAgICAgICAgICAgeDEgPSAiT0UgPSBPcmlnaW5hbCBFeHRyYWN0IChnLzEwMG1MKSIsDQogICAgICAgICAgICAgICAgICAgICAgeDIgPSAiQURGID0gQXBwYXJlbnQgRGVncmVlIG9mIEZlcm1lbnRhdGlvbiAoJSkiLA0KICAgICAgICAgICAgICAgICAgICAgIHgzID0gIkFCVyA9IEV0aGFub2wgKHcvdykiLA0KICAgICAgICAgICAgICAgICAgICAgIHg0ID0gIkVyID0gUmVzaWR1YWwgRXh0cmFjdCAoZy8xMDBtTCkiDQogICAgICAgICAgICAgICAgICAgICAgKQ0KbXRhYmxlMTIzNA0KI3Nob3dfaHRtbChtdGFibGUxMjM0YikNCg0KDQpgYGANCg0KDQoNCg0KDQojIyBjb21wYXJlIG1vcmUgbGluZWFyIG1vZGVscw0KYGBge3J9DQpkZiA8LSBGUEhjYWxjDQpsbTE8LWxtKEZQSF9FdE9IfmRheXNvbmhvcHMsIGRhdGE9ZGYpDQpsbTI8LWxtKEZQSF9FdE9IfmRheXNvbmhvcHMqZm9ybV9vZl9ob3BzLCBkYXRhPWRmKQ0KbG0zPC1sbShGUEhfRXRPSH5kYXlzb25ob3BzKnJvdXNlLCBkYXRhPWRmKQ0KbG00PC1sbShGUEhfRXRPSH5kYXlzb25ob3BzKmZvcm1fb2ZfaG9wcypyb3VzZSwgZGF0YT1kZikNCg0KbXRhYmxlMTIzNCA8LSBtdGFibGUoIk1vZGVsIDEiPWxtMSwiTW9kZWwgMiI9bG0yLCJNb2RlbCAzIj1sbTMsICJNb2RlbCA0Ij1sbTQsDQogICAgICAgICAgICAgICAgICAgIHN1bW1hcnkuc3RhdHM9Yygic2lnbWEiLCJSLXNxdWFyZWQiLCJGIiwicCIsIk4iKSxzaG93LmVxbmFtZXM9VCkNCm10YWJsZTEyMzRiIDwtIHJlbGFiZWwobXRhYmxlMTIzNCwNCiAgICAgICAgICAgICAgICAgICAgICAiKEludGVyY2VwdCkiID0gIkNvbnN0YW50IiwNCiAgICAgICAgICAgICAgICAgICAgICBTRyA9ICJTcGVjaWZpYyBHcmF2aXR5IiwNCiAgICAgICAgICAgICAgICAgICAgICBBQlcgPSAiQUJXID0gRXRoYW5vbCAody93KSIsDQogICAgICAgICAgICAgICAgICAgICAgRXIgPSAiRXIgPSBSZXNpZHVhbCBFeHRyYWN0IChnLzEwMG1MKSINCiAgICAgICAgICAgICAgICAgICAgICApDQptdGFibGUxMjM0DQojc2hvd19odG1sKG10YWJsZTEyMzRiKQ0KYGBgDQoNCg0KDQojIyBUaGUgUiBCb29rIChDcmF3bGV5KSBUYWJsZSAyMC4xOiBub25saW5lYXIgZnVuY3Rpb25zIHVzZWZ1bCBpbiBiaW9sb2d5ICAgDQpUYWJsZSAyMC4xLiBbVXNlZnVsIG5vbi1saW5lYXIgZnVuY3Rpb25zXShodHRwczovL3d3dy5jcy51cGMuZWR1L35yb2JlcnQvdGVhY2hpbmcvZXN0YWRpc3RpY2EvVGhlUkJvb2sucGRmICJNaWNoYWVsIEouIENyYXdsZXkuICBUaGUgUiBib29rLiAgcC4gNzM4IikgRVhQQU5ERUQ6DQoNCg0KDQp8IEZ1bmN0aW9uIENsYXNzIHwgbmFtZSB8IGVxdWF0aW9uIHwgZXhhbXBsZSBjb2RlIHxleGFtcGxlIGFwcGxpY2F0aW9uc3wNCnw6LS0tLS0tLS0tLXw6LS0tLS06fDotLS0tLS0tfDotLS0tLXw6LS0tLS18DQp8ICoqQXN5bXB0b3RpYyBmdW5jdGlvbnMqKnxNaWNoYWVsaXPigJNNZW50ZW58JHkgPVxmcmFje2F4fXsxK2J4fSR8bmxzKGJvbmV+YSphZ2UvKDErYiphZ2UpLHN0YXJ0PWxpc3QoYT04LGI9MC4wOCkpKSB8IGVuenltZSByZWFjdGlvbnMgfA0KfCB8IHwgfCBubHMocmF0ZX5TU21pY21lbihjb25jLGEsYikpIHwgdGJkfA0KfCB8Mi1wYXJhbWV0ZXIgYXN5bXB0b3RpYyBleHBvbmVudGlhbCB8ICR5ID0gYSgxIOKIkiBlXnviiJJieH0gKSQgfG5scyhib25lfmEqKDEtZXhwKC1jKmFnZSkpLHN0YXJ0PWxpc3QoYT0xMjAsYz0wLjA2NCkpIHwgdGJkIHwNCnwgfDMtcGFyYW1ldGVyIGFzeW1wdG90aWMgZXhwb25lbnRpYWwgfCAkeSA9IGEg4oiSIGJlXnviiJJjeH0kIHwgbmxzKGJvbmV+YS1iKmV4cCgtYyphZ2UpLHN0YXJ0PWxpc3QoYT0xMjAsYj0xMTAsYz0wLjA2NCkpIHwgdGJkIHwNCnwgfCB8IHwgbmxzKGJvbmV+U1Nhc3ltcChhZ2UsYSxiLGMpKSB8IHRiZCB8DQp8IHwgfCB8IG5scyhkZW5zaXR5IH4gU1Nsb2dpcyhsb2coY29uY2VudHJhdGlvbiksIGEsIGIsIGMpKSB8IHRiZCB8DQp8KipTLXNoYXBlZCBmdW5jdGlvbnMqKiB8Mi1wYXJhbWV0ZXIgbG9naXN0aWMgfCR5ID0gXGZyYWN7ZV57YStieH19ezEgKyBlXnthK2J4fX0kIHwgfCB0YmQgfA0KfCB8IDMtcGFyYW1ldGVyIGxvZ2lzdGljIHwgJHkgPSBcZnJhY3thfXsxICsgYmVee+KIkmN4fX0kIHwgfCB0YmQgfA0KfCB8IDQtcGFyYW1ldGVyIGxvZ2lzdGljIHwgJHkgPSBhICsgXGZyYWN7Yi1hfXsxICsgZV57KGPiiJJ4KS9kfX0kIHwgbmxzKHdlaWdodH5TU2ZwbChUaW1lLCBhLCBiLCBjLCBkKSkgfCB0YmQgfA0KfCB8IFdlaWJ1bGwgfCAkeSA9IGEg4oiSIGJlXnviiJIoY3heZCl9JCB8IG5scyh3ZWlnaHQgfiBTU3dlaWJ1bGwodGltZSwgQXN5bSwgRHJvcCwgbHJjLCBwd3IpKSB8IHRiZCB8DQp8IHwgR29tcGVydHogfCAkeSA9IGFlXnviiJJiZV574oiSY3h9fSQgfCB8IHRiZCB8DQp8ICoqSHVtcGVkIGN1cnZlcyoqIHwgUmlja2VyIGN1cnZlIHwgJHkgPSBheGVee+KIkmJ4fSQgfCB8IHRiZCB8DQp8IHwgRmlyc3Qtb3JkZXIgY29tcGFydG1lbnQgfCAkeSA9IGsgZXhwKOKIkmV4cChhKXgpIOKIkiBleHAo4oiSZXhwKGIpeCkkIHwgbmxzKGNvbmN+U1Nmb2woRG9zZSwgVGltZSwgYSwgYiwgYykpIHwgdGJkIHwNCnwgfCBCZWxsLXNoYXBlZCB8ICR5ID0gYSBleHAo4oiSQUJTKGJ4KV4yKSQgfCB8IHRiZCB8DQp8IHwgQmlleHBvbmVudGlhbCB8ICR5ID0gYWVee2J4fSDiiJIgY2Vee+KIkmR4fSQgIHwgfCB0YmQgfA0KDQoNCg0KIyMgTWljaGFlbGlzLU1lbnRvbiBtb2RlbCAoQ3Jhd2xleSBwLiA3NDEpDQokeSA9XGZyYWN7YXh9ezErYnh9JA0KDQpgYGB7cn0NCmV4cHQyIDwtIEZQSGNhbGMgJT4lIGRwbHlyOjpmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBkcGx5cjo6c2VsZWN0KEZQSF9FdE9ILCByb3VzZSwgZGF5c29uaG9wcyxicmV3X2RhdGUsIHBvdW5kc19iYmwsZm9ybV9vZl9ob3BzLEFCVy5jb250cm9sX05ILGRheW9mYWRkaXRpb24pDQoNCm15bW9kZWwgPC0gbmxzKEZQSF9FdE9IfmEqZGF5c29uaG9wcy8oMStiKmRheXNvbmhvcHMpLCBkYXRhPWV4cHQyLHN0YXJ0PWxpc3QoYT04LCBiPTAuMDgpKQ0KDQpzdW1tYXJ5KG15bW9kZWwpDQoNCmBgYA0KDQoNCg0KYGBge3J9DQp4IDwtIHNlcSgwLDUwLDAuMSkNCnl2IDwtIHByZWRpY3QobXltb2RlbCwgbGlzdChkYXlzb25ob3BzPXgpKQ0Ke3Bsb3QoZXhwdDIkZGF5c29uaG9wcywgZXhwdDIkRlBIX0V0T0gsIHBjaD0yMSwgY29sPSJwdXJwbGUiLCBiZz0iZ3JlZW4iKQ0KICBsaW5lcyh4LHl2LGNvbD0iYmx1ZSIpfQ0KDQpgYGANCg0KDQpTbyB1c2luZyB0aGUgTWljaGFlbGlzLU1lbnRlbiBlcXVhdGlvbiBhcyBvdXIgbW9kZWwsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBob3AtaW5kdWNlZCBldGhhbm9sIHByb2R1Y3Rpb24gKEZQSF9FdE9IKSBhbmQgaG9wIGNvbnRhY3QgdGltZSAoZGF5c29uaG9wcykgd291bGQgYmUgZXhwcmVzc2VkIGFzOg0KJEZQSF9FdE9IID0gXGZyYWN7MC4xODM1NzIqZGF5c29uaG9wc317MSswLjA2NjM4OCpkYXlzb25ob3BzfSQNCg0KDQoNCiMjIG5vbmxpbmVhciBtb2RlbHMgb2YgaG9wLWluZHVjZWQgUGxhdG8gZHJvcCBvdmVyIHRpbWUgd2l0aCBsaWJyYXJ5KGRyYykgDQpgYGB7ciBkcmMubW9kZWxzfQ0KZGYgPC0gRlBIY2FsYyAlPiUgZmlsdGVyKGV4cHQ9PSIgMkEiKSANCmRmJFBMQVRPLmhvcGRyb3AgPC0gZGYkaW5pdGlhbF9wbGF0byArIGRmJGRlbHRhLnBsYXRvDQoNCnggPC0gZGYkZGF5c29uaG9wcw0KeSA8LSBkZiRQTEFUTy5ob3Bkcm9wDQpncm91cDwtIGFzLmZhY3RvcihkZiRzcGVjaWFsX2NvbmRpdGlvbnMpICAgI3JvdXNlDQpjb2xzIDwtIGFzLm51bWVyaWMoZ3JvdXApDQpsZWdlbmQuY29scyA8LSBhcy5udW1lcmljKGFzLmZhY3RvcihsZXZlbHMoZ3JvdXApKSkNCg0KIyMgRml0dGluZyBtb2RlbHMgdXNpbmcgZnVuY3Rpb24gZHJtIGZyb20gbGlicmFyeShkcmMpLiBzZWUgPGh0dHA6Ly9yc3RhdHM0YWcub3JnL2Rvc2UtcmVzcG9uc2UtY3VydmVzLmh0bWw+IGZvciBvdmVydmlldyBhbmQgPGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9kcmMvPiBmb3IgbGlzdCBvZiBtb2RlbHMgKGZjdCB2YWx1ZXMpIGFuZCBvdGhlciBmdW5jdGlvbnMgYXZhaWxhYmxlIGluIGRyYw0KDQojIyBjcmVhdGUgbW9kZWxzDQptLkxMLjM8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTC4zKCkpICMzLXBhcmFtZXRlciBsb2dpc3RpYyAobG93ZXIgbGltaXQgYXQgMCkNCiNtLkxMLjN1PC1kcm0oeSB+eCwgZmN0ID0gTEwuM3UoKSkgIzMtcGFyYW1ldGVyIGxvZ2lzdGljICh1cHBlciBsaW1pdCBhdCAxKQ0KbS5MTC40PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTEwuNCgpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljICANCiMgZnJvbSA/P0xMLjQ6ICBmKHgpID0gYyArIFxmcmFje2QtY317MStcZXhwKGIoXGxvZyh4KS1cbG9nKGUpKSl9IG9yIGluIGFub3RoZXIgcGFyYW1ldGVyaXNhdGlvbiAoY29udmVydGluZyB0aGUgdGVybSBcbG9nKGUpIGludG8gYSBwYXJhbWV0ZXIpIGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsxK1xleHAoYihcbG9nKHgpLVx0aWxkZXtlfSkpfSAgDQptLkwuNCA8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMLjQoKSkgI2NoYW5naW5nIHRoZSBmY3QgPSBMTC40KCkgdG8gZmN0ID0gTC40KCkgYWxsb3dzIHBsb3R0aW5nIG9uIGEgbG9nMTAgc2NhbGUNCm0uTEwyLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTDIuNCgpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIHdpdGggbG9nKGUpIHJhdGhlciB0aGFuIGUgYXMgYSBwYXJhbWV0ZXIgIA0KbS5MTDIuNTwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IExMMi41KCkpICNHZW5lcmFsaXNlZCBsb2ctbG9naXN0aWMNCiMgZnJvbSA/P0xMLjIuNTogICBmKHgpID0gYyArIFxmcmFje2QtY317KDErXGV4cChiKFxsb2coeCktZSkpKV5mfQ0KbS5MTC41PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTEwuNSgpKSAjNS1wYXJhbWV0ZXIgbG9naXN0aWMgIA0KbS5XMS40PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gVzEuNCgpKSAjNC1wYXJhbWV0ZXIgV2VpYnVsbDEgIA0KbS5XMi40PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gVzIuNCgpKSAjNC1wYXJhbWV0ZXIgV2VpYnVsbDIgIA0KbS5CQy41PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gQkMuNSgpKSAjNS1wYXJhbWV0ZXIgQnJhaW4tQ291c2VucyAoaG9ybWVzaXMpDQptLkFSLjM8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBBUi4zKCkpICMzLXBhcmFtZXRlciBTaGlmdGVkIGFzeW1wdG90aWMgcmVncmVzc2lvbg0KI20uTU0uMjwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IE1NLjIoKSkgIzItcGFyYW1ldGVyIE1pY2hhZWxpcy1NZW50ZW4NCm0uTU0uMzwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IE1NLjMoKSkgIzMtcGFyYW1ldGVyIE1pY2hhZWxpcy1NZW50ZW4NCg0KDQojI3Bsb3QNCiNwYXIobWZyb3cgPSBjKDEsMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQoNCntwbG90KHggLCB5LCBjb2w9Y29scywgbWFpbj0icmF3IGRhdGEiKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZXZlbHMoZ3JvdXApLCBwY2g9MTYsIGNvbD1sZWdlbmQuY29scyl9DQoNCnBsb3QobS5MTC4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC4zIChsb3dlciBsaW1pdCBhdCAwKSIpDQpwbG90KG0uTEwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwuNCBmb3VyLXBhcmFtZXRlciBsb2ctbG9naXN0aWMiKQ0KcGxvdChtLkwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTC40IikNCnBsb3QobS5MTC41LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC41IikNCnBsb3QobS5MTDIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwyLjQgZm91ci1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5MTDIuNSwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iR2VuZXJhbGlzZWQgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5XMS40LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJXZWlidWxsMSIpDQpwbG90KG0uVzIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iV2VpYnVsbDIiKQ0KcGxvdChtLkJDLjUsIHR5cGU9J2FsbCcsY29sPWNvbHMsIG1haW49IkJyYWluLUNvdXNlbnMgKGhvcm1lc2lzKSIpDQpwbG90KG0uQVIuMywgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iU2hpZnRlZCBhc3ltcHRvdGljIHJlZ3Jlc3Npb24iKQ0KI3Bsb3QobS5NTS4yLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIyLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCnBsb3QobS5NTS4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIzLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCg0KYGBgDQoNCg0KDQojIyAgU3BlZXJzMjAwMzogTm9u4oCQTGluZWFyIE1vZGVsbGluZyBvZiBJbmR1c3RyaWFsIEJyZXdpbmcgRmVybWVudGF0aW9ucw0KUi4gQWxleCBTcGVlcnMsIFBldGVyIFJvZ2VycywgQnJ1Y2UgU21pdGgNCkouIEluc3QuIEJyZXcuIDEwOSgzKSwgMjI54oCTMjM1LCAyMDAzIA0KPGh0dHBzOi8vZG9pLm9yZy8xMC4xMDAyL2ouMjA1MC0wNDE2LjIwMDMudGIwMDE2My54Pg0KRnJlZSBBY2Nlc3MgZnJvbSB0aGUgSW5zdGl0dXRlIG9mIEJyZXdpbmchDQo8aHR0cHM6Ly9vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvMTAuMTAwMi9qLjIwNTAtMDQxNi4yMDAzLnRiMDAxNjMueD4NCg0KRm9sbG93aW5nIFNwZWVyczIwMDMsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBwbGF0byBhbmQgdGltZSBpbiBwcmltYXJ5IGZlcm1lbnRhdGlvbnMgaXMgd2VsbCBtb2RlbGVkIGJ5IGEgZm91ciBwYXJhbWV0ZXIgbG9naXN0aWMgbW9kZWw6DQoNClBMQVRPID0gUElORiArIChQX0QgLyAoMSsgKEVYUCgtQiooVElNRS1NKSkpKSkNCg0Kb3INCg0KJFBMQVRPID0gUF97aW5mfSsgXGZyYWN7UF9EfXsxK2Veey1CKHQtTSl9fSQNCg0Kd2hlcmUNCg0KKiB0ID0gdGltZSBpbnRvIGZlcm1lbnRhdGlvbg0KKiBQX0QgPSAgY2hhbmdlIGluIFBsYXRvIGR1cmluZyBmZXJtZW50YXRpb24gKE9FIC0gUF97aW5mfSkNCiogQiB+IG1heGltdW0gZmVybWVudGF0aW9uIHJhdGUNCiogTSA9IGZlcm1lbmF0aW9uIG1pZHBvaW50DQoqIFBfaW5mID0gZmluYWwgZ3Jhdml0eQ0KDQoNCnNvIHRoZW4gZm9yIG91ciBjYXNlDQoNCiRQTEFUTyA9IFBfe2luZn0rICBcZnJhY3tPRS1QX3tpbmZ9fXsxK2Veey1GX3ttYXh9KGRheXNvbmhvcHMtTSl9fSQNCg0KUExBVE8gPSBQSU5GICsgKFAwIC8gKDErIChFWFAoQiooSE9VUlMtTSkpKSkpDQoNCg0KIyMgIE1hY0ludG9zaDIwMTY6IEFuIEV4YW1pbmF0aW9uIG9mIFN1YnN0cmF0ZSBhbmQgUHJvZHVjdCBLaW5ldGljcyBEdXJpbmcgQnJld2luZyBGZXJtZW50YXRpb25zDQpBbmRyZXcgSi4gTWFjSW50b3NoLCBNYXJpYSBKb3NleSwgUi4gQWxleCBTcGVlcnMNCltKLiBBbS4gU29jLiBCcmV3LiBDaGVtLiA3NCg0KSwgMjUwLTI1NywgMjAxNl0oaHR0cHM6Ly93d3cuYXNiY25ldC5vcmcvcHVibGljYXRpb25zL2pvdXJuYWwvdm9sLzIwMTYvUGFnZXMvQVNCQ0otMjAxNi00NzUzLTAxLmFzcHgpDQoNCiJ0aGUgZml2ZS1wYXJhbWV0ZXIgbWV0aG9kIHdhcyBzZWxlY3RlZCBvdmVyIHRoZQ0Kc3RhbmRhcmQgZm91ci1wYXJhbWV0ZXIgbG9naXN0aWMgZGV0YWlsZWQgaW4gQVNCQyBNZXRob2QgWWVhc3QtDQoxNCBiZWNhdXNlIGl0IHlpZWxkZWQgYSBzdXBlcmlvciBmaXQgd2hlbiBhc3Nlc3NlZCB3aXRoIGFuIGV4dHJhDQpzdW0tb2Ytc3F1YXJlcyBGIHRlc3QuIFdlIGZ1cnRoZXIgbm90ZSB0aGF0IGl0IGlzIG9ubHkgc3RhdGlzdGljYWxseQ0KYWR2YW50YWdlb3VzIHRvIGFwcGx5IHRoZSBmaXZlLXBhcmFtZXRlciBtb2RlbCB3aGVuIG1hbnkgZGF0YQ0KcG9pbnRzIGFyZSBhdmFpbGFibGUgKDcyIGZyb20gdGhpcyBleHBlcmltZW50IGFzIG9wcG9zZWQgdG8gMTANCndoZW4gdXNpbmcgWWVhc3QtMTQpLiINCg0KTWFjSW50b3NoOiAgJFBfeyh0KX0gPVxmcmFje1BfaSAtIFBfZX17KDErcyAqIGVeey1CKHQtTSl9KV57MS9zfX0kDQoNCmxpYnJhcnkoZHJjKQ0KZ2V0TWVhbkZ1bmN0aW9ucygpDQoNCiogZHJjIExMLjQ6ICAkZih4KSA9IGMgKyBcZnJhY3tkLWN9ezErXGV4cChiKFxsb2coeCktXHRpbGRle2V9KSl9JCAgIzRwYXJhbSBsb2dpc3RpYw0KKiBkcmMgTEwuNTogICRmKHgpID0gYyArIFxmcmFje2QtY317KDErXGV4cChiKFxsb2coeCktZSkpKV5mfSQgICAgICAjNXBhcmFtIGxvZ2lzdGljDQoqIGRyYyBHLjQgJGYoeCkgPSBjICsgKGQtYykoXGV4cCgtXGV4cChiKHgtZSkpKSkkICAgICAgICAgICAgICAgICAgICM0cGFyYW0gR29tcGVydHogIA0KKiBkcmMgVzEuNCAkZih4KSA9IGMgKyAoZC1jKSBcZXhwKC1cZXhwKGIoXGxvZyh4KS1cbG9nKGUpKSkpJCAgICAgICAjNHBhcmFtIFdlaWJ1bGwxDQoqIGRyYyBXMi40ICRmKHgpID0gYyArIChkLWMpICgxIC0gXGV4cCgtXGV4cChiKFxsb2coeCktXGxvZyhlKSkpKSkkICM0cGFyYW0gV2VpYnVsbDINCg0KDQojIyBtdWx0aWxldmVsIGxvZ2lzdGljIG1vZGVscyB3aXRoIGxpYnJhcnkoZHJjKQ0KYGBge3IgZHJjLm1vZGVscy5sZXZlbHN9DQpkZiA8LSBGUEhjYWxjICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpIA0KZGYkUExBVE8uaG9wZHJvcCA8LSBkZiRpbml0aWFsX3BsYXRvICsgZGYkZGVsdGEucGxhdG8NCnggPC0gZGYkZGF5c29uaG9wcw0KeSA8LSBkZiRQTEFUTy5ob3Bkcm9wDQpteWxldmVsczwtIGFzLmZhY3RvcihkZiRzcGVjaWFsX2NvbmRpdGlvbnMpICAjcm91c2UNCmNvbHMgPC0gYXMubnVtZXJpYyhteWxldmVscykNCmxlZ2VuZC5jb2xzIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKGxldmVscyhteWxldmVscykpKQ0KDQojIyBjcmVhdGUgbW9kZWxzDQojbS5MTC40PC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTEwuNChuYW1lcyA9IGMoIlNsb3BlIiwgIkxvd2VyIiwgIlVwcGVyIiwgIk1pZHBvaW50IG9yIEVENTAiKSkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgKGdlbmVyYWwgcGFyYW1ldGVycykNCiNtLkxMLjQ8LWRybSh5IH54LG15bGV2ZWxzLCBmY3QgPSBMTC40KG5hbWVzID0gYygiU2xvcGUiLCAiTG93ZXIiLCAiVXBwZXIiLCAiRUQ1MCIpKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyAoRG9zZS1SZXNwb25zZSBwYXJhbWV0ZXJzKQ0KbS5MTC40PC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTEwuNChuYW1lcyA9IGMoIkZfbWF4IiwgIlBfaW5mIiwgIk9FIiwgIk0iKSkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgIA0KbS5MLjQgPC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTC40KCkpICNjaGFuZ2luZyB0aGUgZmN0ID0gTEwuNCgpIHRvIGZjdCA9IEwuNCgpIGFsbG93cyBwbG90dGluZyBvbiBhIGxvZzEwIHNjYWxlDQptLkxMMi40PC1kcm0oeSB+eCxteWxldmVscywgZmN0ID0gTEwyLjQoKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyB3aXRoIGxvZyhlKSByYXRoZXIgdGhhbiBlIGFzIGEgcGFyYW1ldGVyICANCm0uTEwyLjU8LWRybSh5IH54LG15bGV2ZWxzLCBmY3QgPSBMTDIuNSgpKSAjR2VuZXJhbGlzZWQgbG9nLWxvZ2lzdGljDQptLkxMLjU8LWRybSh5IH54LG15bGV2ZWxzLCBmY3QgPSBMTC41KCkpICM1LXBhcmFtZXRlciBsb2dpc3RpYyAgDQoNCm15bW9kZWwgPC0gbS5MTC40DQoNCmNvbmZpbnQobXltb2RlbCkNCg0Ke3Bsb3QobXltb2RlbCxsb2c9IiIsICBicm9rZW4gPSBUUlVFLCBiY29udHJvbCA9IGxpc3Qoc3R5bGUgPSAic2xhc2giKSwgY29sID0gYygyLDYsMywyMyw1NiksIG1haW4gPSAiaW1wYWN0IG9mIG15bGV2ZWxzIG9uIGVzdGltYXRlZCBtb2RlbCBwYXJhbWF0ZXJzIikNCmxlZ2VuZCgzNCwgMy43LCBsZWdlbmQ9Yygicm91c2VkIiwic3RpbGwiKSwgZmlsbD1jKCJyZWQiLCAicGluayIpLCB0aXRsZT0ibXlsZXZlbHMiKX0NCg0KbXltb2RlbA0Kc3VtbWFyeShteW1vZGVsKQ0KDQpgYGANCg0KRWFjaCBsZXZlbCAocm91c2UgYW5kIHN0aWxsKSBoYXMgYSB1bmlxdWUgY3VydmUgc2hhcGUgYW5kIHRoZXJlZm9yZSBkaWZmZXJlbnQgcGFyYW1ldGVycyBmb3IgYSBiZXN0LWZpdCBsb2ctbG9naXN0aWMgbW9kZWwuICBOb3cgdGhhdCB3ZSBrbm93IHRoaXMsIGl0IGlzIHByYWN0aWNhbCB0byBhZGRyZXNzIGVhY2ggc2V0IG9mIGNvbmRpdGlvbnMgc2VwYXJhdGVseSwgYW5kIGJhc2VkIG9uIHRoZSBMTC40IG1vZGVsIGFib3ZlIHdlIGhhdmU6DQoNCmZvciByb3VzZSA9IFRSVUU6DQoNCiRQTEFUT197cm91c2VkfSA9IFBfe2luZn0rICBcZnJhY3tPRS1QX3tpbmZ9fXsxK2Veey1GX3ttYXh9KGRheXNvbmhvcHMtTSl9fSQNCg0Kb3INCg0KJFBMQVRPX3tyb3VzZWR9ICA9IDEuODA5ICsgXGZyYWN7MS45NH17MStlXnstMS4zOTI1KGRheXNvbmhvcHMtMTIuMjcyKX19JA0KDQphbmQNCg0KJFBMQVRPX3tzdGlsbH0gID0gMi4xNDkgKyBcZnJhY3sxLjYwfXsxK2Veey0xLjgwMDQoZGF5c29uaG9wcy03Ljk1OCl9fSQNCg0KUGxvdHRpbmcgYW5kIGFuYWx5c2lzIG9wdGlvbnMgZm9yIG11bHRpbGV2ZWwgbW9kZWxzIGFyZSBsaW1pdGVkLCBzbyBsZXQncyBtb3ZlIGZvcndhcmQgd2l0aCBhIHNpbmdsZSBzZXQgb2YgY29uZGl0aW9ucyBhbmQgYSAnc2ltcGxlJyBzaW5nbGUtbGV2ZWwgbG9naXN0aWMgbW9kZWwgKGV4cHQ9MmEsIHJvdXNlPVRSVUUpLg0KDQoNCiMjIHVuaWxldmVsIGxvZ2lzdGljIG1vZGVsIHdpdGggY29uZmlkZW5jZSBpbnRlcnZhbHMgDQpgYGB7cn0NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIiAmIHJvdXNlPT1UUlVFKSANCmRmJFBMQVRPLmhvcGRyb3AgPC0gZGYkaW5pdGlhbF9wbGF0byArIGRmJGRlbHRhLnBsYXRvDQp4IDwtIGFzLm51bWVyaWMoZGYkZGF5c29uaG9wcykNCnkgPC0gZGYkUExBVE8uaG9wZHJvcA0KDQojIyBjcmVhdGUgbW9kZWwNCm15bW9kZWw8LWRybSh5IH54LCBmY3QgPSBMTC40KCkpICM0LXBhcmFtZXRlciBsb2dpc3RpYyB3aXRoIGxpYnJhcnkoZHJjKQ0KDQpwbG90KG15bW9kZWwsIG1haW4gPSAiZGVmYXVsdCIpDQpwbG90KG15bW9kZWwsIGxvZz0iIiwgbWFpbiA9ICJub24tbG9nYXJpdGhtaWMgeC1heGlzIikNCg0KIyBjcmVhdGUgcHJlZGljdGlvbiBpbnRlcnZhbHMNCm5ld2RhdGEgPSBkYXRhLmZyYW1lKHkgPSB5LCB4ID0geCkNCmNvbmY5NSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSxpbnRlcnZhbCA9ICJjb25maWRlbmNlIiwgbGV2ZWwgPSAwLjk1KSkNCmNvbmY5NSR4IDwtIHgNCnByZWQ5NSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIsIGxldmVsID0gMC45NSkpDQpwcmVkOTUkeCA8LSB4DQpwcmVkOTk5IDwtIGFzLmRhdGEuZnJhbWUocHJlZGljdChteW1vZGVsLCBuZXdkYXRhLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIiwgbGV2ZWwgPSAwLjk5OSkpDQpwcmVkOTk5JHggPC0geA0KDQojIEFzeW1tZXRyaWMgcmliYm9ucyBiYXNlZCBvbiBwcmVkaWN0aW9uIGludGVydmFscw0KI25ld2RhdGEgPC0gZGF0YS5mcmFtZSh5ID0gYygzLjUsIDMsIDIuOSwgMi41LDEuOCwxLjcpLCB4ID0gYygxLDUsMTAsMjAsMzAsNDApKQ0KI25ld2RhdGEgPC0gdGlkeWZlcm1pICU+JSBmaWx0ZXIoYmF0Y2ggPSBTRUxFQ1RCQVRDSCkgJT4lIHNlbGVjdChkYXlzLHBsYXRvKSANCnFwbG90KGRhdGE9cHJlZDk1LCB4PXgsIHk9UHJlZGljdGlvbiwgeW1pbj1Mb3dlciwgeW1heD1QcmVkaWN0aW9uLCBnZW9tPSJyaWJib24iLCBmaWxsPUkoInJlZCIpLCBhbHBoYT1JKDAuMikpICsNCmdlb21fcmliYm9uKGRhdGE9cHJlZDk1LCBhZXMoeD14LCB5bWluPVByZWRpY3Rpb24sIHltYXg9VXBwZXIpLCBmaWxsPUkoImJsdWUiKSwgYWxwaGE9SSgwLjIpKSArDQpnZW9tX2xpbmUoZGF0YT1wcmVkOTUsIGFlcyh4PXgsIHk9UHJlZGljdGlvbiksIGNvbG9yPUkoImdyZWVuIiksIGx3ZD0xKSsNCmdlb21fcG9pbnQoZGF0YT1uZXdkYXRhLCBhZXMoeD14LCB5PXksIHltaW49TlVMTCwgeW1heD1OVUxMKSwgc2l6ZT0xLCBjb2w9ImJsdWUiKSsNCiB5bGFiKCJ5IikNCg0KIyBWaXN1YWxpc2UgaW50ZXJ2YWxzDQojIGJhc2VkIG9uIE1hdXJpdHMgRXZlcnMgKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzQ5NDQ0NDg5L25vbmxpbmVhci1yZWdyZXNzaW9uLXByZWRpY3Rpb24taW4tcj9ycT0xKQ0KZGF0YS5mcmFtZSh4PXgsIHk9eSkgJT4lIGdncGxvdChhZXMoeCwgeSkpICsNCmdlb21fcG9pbnQoKSArDQpnZW9tX2xpbmUoZGF0YSA9IHByZWQ5NSwgYWVzKHggPSB4LCB5ID0gUHJlZGljdGlvbikpICsNCmdlb21fcmliYm9uKGRhdGEgPSBwcmVkOTk5LCBhZXMoeCA9IHgsIHltaW4gPSBMb3dlciwgeW1heCA9IFVwcGVyKSxmaWxsPUkoInBpbmsiKSxhbHBoYSA9IDAuNCkrDQpnZW9tX3JpYmJvbihkYXRhID0gcHJlZDk1LCBhZXMoeCA9IHgsIHltaW4gPSBMb3dlciwgeW1heCA9IFVwcGVyKSxmaWxsPUkoImdyZWVuIiksYWxwaGEgPSAwLjQpKw0KZ2VvbV9yaWJib24oZGF0YSA9IGNvbmY5NSwgYWVzKHggPSB4LCB5bWluID0gTG93ZXIsIHltYXggPSBVcHBlciksZmlsbD1JKCJwdXJwbGUiKSxhbHBoYSA9IDAuNCk7DQoNCmBgYA0KDQpOb3RlOiAgSWYgeW91IGNvbXBhcmUgdGhlIG11bHRpLWxldmVsIG1vZGVsIHBhcmFtZXRlciBlc3RpbWF0ZXMgZm9yIHJvdXNlPVRSVUUgd2l0aCB0aG9zZSBmb3IgdW5pbGV2ZWwgbW9kZWwgYmFzZWQgb24gcm91c2U9VFJVRSBzdWJzZXQsIHRoZSByZXN1bHRzIGFyZSBxdWl0ZSBzaW1pbGFyLCBidXQgbm90IGlkZW50aWNhbCENCg0KIyMgcGxvdCByZXNpZHVhbHMgb2Ygbm9ubGluZWFyIG1vZGVscyBnZW5lcmF0ZWQgaW4gbGlicmFyeShkcmMpDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQoNCnBsb3QobS5NTS4zJHByZWRyZXMpDQpwbG90KG0uTEwuMyRwcmVkcmVzKQ0KcGxvdChtLkxMLjQkcHJlZHJlcykNCnBsb3QobS5MTC41JHByZWRyZXMpDQpgYGANCg0KDQojIHN1bW1hcnkNCg0KRm9yIGNhc2VzIHdoZXJlIDIwMTYgQ2VudGVubmlhbCBob3BzIGFyZSBhZGRlZCB0byB0aGlzIHBhcnRpY3VsYXIgYmFzZSBiZWVyIChzYW1wbGVkIGludG8gMTJveiBib3R0bGVzKSBuZWFyIHRoZSBlbmQgb2YgcHJpbWFyeSBmZXJtZW50YXRpb24gYXQgcm9vbSB0ZW1wZXJhdHVyZSBhbmQgYm90dGxlcyByZW1haW4gdW50b3VjaGVkIChzdGlsbCksIHRoZSA0LXBhcmFtZXRlciBsb2dpc3RpYyBlcXVhdGlvbiB3aXRoIHRoZSBmb2xsb3dpbmcgc2V0cyBvZiBwYXJhbWV0ZXJzIGlzIGEgcmVhc29uYWJsZSBtb2RlbCBvZiB0aGUgc3Vic2VxdWVudCBob3AtaW5kdWNlZCBwbGF0byBkcm9wOg0KDQokUExBVE9fe3N0aWxsfSAgPSAyLjE0OSArIFxmcmFjezEuNjB9ezErZV57LTEuODAwNChkYXlzb25ob3BzLTcuOTU4KX19JA0KDQoNClRoZSBmb2xsb3dpbmcgNC1wYXJhbWV0ZXIgbG9naXN0aWMgZXF1YXRpb24gaXMgYSByZWFzb25hYmxlIG1vZGVsIGZvciBjYXNlcyBpZGVudGljYWwgdG8gdGhlIGFib3ZlLCBidXQgd2hlcmUgYm90dGxlcyBhcmUgcm91c2VkIG9uY2UgZGFpbHk6DQoNCiRQTEFUT197cm91c2VkfSAgPSAxLjgwOSArIFxmcmFjezEuOTR9ezErZV57LTEuMzkyNShkYXlzb25ob3BzLTEyLjI3Mil9fSQNCg0KV2hlcmVhcyB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMgZm9yIHRoZSA1LXBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbHMgd2FzIHNpbWlsYXIgdG8gdGhvc2Ugb2J0YWluZWQgZm9yIHRoZSA0LXBhcmFtZXRlciBlcXVhdGlvbiwgYSB2aXN1YWwgaW5zcGVjdGlvbiByZXZlYWxzIHRoYXQgdGhlIDQtcGFyYW1ldGVyIGVxdWF0aW9uIGlzIHByb2JhYmx5IHN1cGVyaW9yLCBpbiB0aGF0IHRoZSA0LXBhcmFtZXRlciBmaXQgYXBwYXJlbnRseSBkb2VzIG5vdCBbZmFsc2VseT9dIHByZWRpY3QgYSBsb25nIGxhZyB0aW1lIGluIHRoZSBjYXNlIG9mIHVucm91c2VkIHNhbXBsZXMuDQoNCk9ubHkgYSBzaW5nbGUgdmFyaWFibGUgKHJvdXNpbmcgdnMuIHN0aWxsKSBhbW9uZyBzZXZlcmFsIGh1bmRyZWQgdmFyaWFibGVzIGVuY29tcGFzc2luZyByYXcgbWF0ZXJpYWxzLCBtYWNoaW5lcywgcGVvcGxlIGFuZCBwcm9jZXNzIHJlc3VsdGVkIGluIHF1aXRlIGRpZmZlcmVudCBlc3RpbWF0ZWQgcGFyYW1ldGVycyBmb3IgYSA0LXBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbC4gIFRoZXNlIHJlc3VsdHMgaGlnaGxpZ2h0IHRoZSBmYWN0IHRoYXQgYWxsIHByb2Nlc3MgdmFyaWFibGVzIHRoYXQgc2lnbmlmaWNhbnRseSBpbXBhY3QgdGhlIGVuZHBvaW50cyB3ZSB3aXNoIHRvIGNvbnRyb2wgbXVzdCB0aGVtc2VsdmVzIGJlIGRlZmluZWQsIGNvbnRyb2xsZWQgYW5kL29yIGRvY3VtZW50ZWQgaWYgbW9kZWxzIHN1Y2ggYXMgdGhlc2UgYXJlIHRvIGJlIHVzZWQgdG8gY2hhcmFjdGVyaXplIHRoZSBwcm9jZXNzIGluIHF1ZXN0aW9uLiAgDQoNCg0KIyBiaWdjaHVua190aWR5dHJhbnNmb3JtDQpgYGB7cn0NCm15ZGF0YSA8LXJlYWQuY3N2KCJ1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0IixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICAjIyBTUEVDSUZZIGZpbGVuYW1lDQoNCiMjIGZpbGwgaW4gTUlTU0lORyBEQVRBIDooICBhZGQgdGltZSB6ZXJvIGRhdGEgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzKQ0KDQojIyBuZXN0ZWQgaWZlbHNlIHRvIGFkZCB0aW1lIHplcm8gcGxhdG8gdmFsdWVzIGZvciBlYWNoIGV4cHQ6DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZSguLCBpbml0aWFsX3BsYXRvID0gaWZlbHNlKGV4cHQ9PSIgMkEiLCAzLjc0LCBpZmVsc2UoZXhwdD09IiAxQSIsIDMuNjksIGlmZWxzZShleHB0PT0iIDFCIiwgMy41MywgaWZlbHNlKGV4cHQ9PSIgM0IiLCAzLjU0LCBpZmVsc2UoZXhwdD09IiA0QiIsIDQuNjYsIE5BKSkpKSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiMjIGFkZCBjb21wbGV0ZSB0aW1lLXplcm8gYW50b24gcGFhciBkYXRhIGZvciB0aW1lIHNlcmllcyAoZXhwdDJBKQ0KIyMgcXVhZHJ1cGxlLXVwIGVhY2ggdHJpcGxpY2F0ZSB0aW1lLXplcm8gbWVhc3VyZW1lbnQgKG9uZSBjb3B5IGZvciBlYWNoIE5IL0RIICYgcm91c2Uvc3RpbGwgY29tYm8pDQojIyBub3RlOiAgdGhpcyBwcmFjdGljZSBpcyBvZiBxdWVzdGlvbmFibGUgc3RhdGlzdGljYWwgcmlnb3IuICBJbiByZXRyb3NwZWN0IGl0IHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgKGFuZCBhIGxvdCBlYXNpZXIpIHRvIGp1c3QgcnVuIDEyIHNhbXBsZXMgdGhyb3VnaCBBbnRvbiBvbiBkYXkwIQ0KdGltZXplcm8gPC0gbXlkYXRhICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBhcnJhbmdlKHNwZWNpYWxfZ3JvdXApICU+JSBzbGljZSgxOjEyKQ0KI2RhdGEuZW50cnkodGltZXplcm8pDQp0aW1lemVybyRUZXN0X0RhdGUgPC0gcmVwKGMoIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIpLDQpDQp0aW1lemVybyRUZXN0LnRpbWUgPC0gcmVwKGMoIjM6MjYgUE0iLCAiMzozMCBQTSIsICIzOjM0IFBNIiksNCkNCnRpbWV6ZXJvJEFCViA8LSA2LjY4DQp0aW1lemVybyRBQlcgPC0gNS4yDQp0aW1lemVybyRPRSA8LSAxNS45NQ0KdGltZXplcm8kRXIgPC0gNi4wOA0KdGltZXplcm8kRWEgPC0gcmVwKGMoMy43NCwgMy43MywgMy43NCksNCkNCnRpbWV6ZXJvJFNHIDwtIHJlcChjKDEuMDE0NjIsIDEuMDE0NjEsIDEuMDE0NjIpLDQpDQp0aW1lemVybyRSREYgPC0gcmVwKGMoNjMuODgsIDYzLjg5LCA2My44OCksNCkNCnRpbWV6ZXJvJEFERiA8LSByZXAoYyg3Ni41OCwgNzYuNjAsIDc2LjU4KSw0KQ0KdGltZXplcm8kQ2Fsb3JpZXMgPC0gcmVwKGMoMjE1LjM3LDIxNS4zNCwyMTUuMzUpLDQpDQp0aW1lemVyb1t0aW1lemVybyRob3A9PSJESCIsXSRjb250YWN0X2RheXMgPC0wDQp0aW1lemVyb1ssYygyODozMSldIDwtIDANCm15ZGF0YSA8LSByYmluZCh0aW1lemVybyxteWRhdGEpDQp3cml0ZS5jc3YobXlkYXRhLCAicXVpY2tpZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm15ZGF0YTwtcmVhZC5jc3YoInF1aWNraWUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojIyMgIHRoaXMgaXMgYSByb3V0aW5lIGZvciBpbnNwZWN0aW5nIGRhdGEgIyMjDQpteWRhdGF0eXBlczwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoc2FwcGx5KG15ZGF0YSwgY2xhc3MpKSkgICMjIHRhYmxlIG9mIGRhdGF0eXBlcw0KbXlkYXRhdHlwZXMkaGVhZGVycyA8LSByb3duYW1lcyhteWRhdGF0eXBlcykgICAgIyMgY29udmVydCByb3duYW1lcyB0byB2YWx1ZXMNCm5hX2NvdW50IDwtYXMuZGF0YS5mcmFtZShzYXBwbHkobXlkYXRhLCBmdW5jdGlvbih5KSBzdW0obGVuZ3RoKHdoaWNoKGlzLm5hKHkpKSkpKSkgIyMgY291bnQgTkFzIGJ5IGNvbHVtbg0KbXlkYXRhT3ZlcnZpZXc8LWFzLmRhdGEuZnJhbWUoY2JpbmQobmFfY291bnQsbXlkYXRhdHlwZXMpKSAgIyMgdGFibGUgb2YgTkEgY291bnRzIGFuZCBkYXRhdHlwZXMNCm5hbWVzKG15ZGF0YU92ZXJ2aWV3KSA8LSBjKCJOQV9jb3VudCIsIkRhdGFfQ2xhc3MiLCAiSGVhZGVyIikNCnN1bShpcy5uYShteWRhdGEpKSAgIyMgdG90YWwgY291bnQgb2YgTkEgdmFsdWVzIGluIGVudGlyZSBzaGVldCAoMjMxIGluIHRoaXMgY2FzZSkNCm15ZGF0YU92ZXJ2aWV3ICU+JSBmaWx0ZXIoTkFfY291bnQ+MCkgICAgIyMgYnJlYWtkb3duIG9mIE5BIHZhbHVlcw0KDQpkYXRlY29sczwtIGRwdXQobmFtZXMoZHBseXI6OnNlbGVjdChteWRhdGEsIG1hdGNoZXMoImRhdGUiKSkpKSAgIyMgaGVhZGVycyBjb250YWluaW5nIHN0cmluZyAiZGF0ZSIgPT4gYygiYnJld19kYXRlIiwgInNhbXBsZV9jb2xsZWN0aW9uX2RhdGUiLCAiZHJ5aG9wX2RhdGUiLCAiVGVzdF9EYXRlIikNCiMjIEZPUkxPT1ANCmZvciAoaWNvbCBpbiBkYXRlY29scykgew0KICBuZXdjb2wgPSBwYXN0ZTAoaWNvbCwiLlBPU0lYY3QiKQ0KICMgcHJpbnQobmV3Y29sKQ0KICBteWRhdGFbLCBuZXdjb2xdID0gYXMuUE9TSVhjdChteWRhdGFbLCBpY29sXSxmb3JtYXQgPSAiJW0vJWQvJVkiKSAjIyMgIENSRUFURSBORVcgY29sdW1ucyB3aXRoIFBPU0lYY3QgICANCiMgIG15ZGF0YVssIG5ld2NvbF0gPSBhcy5QT1NJWGN0KGFzLm51bWVyaWMobXlkYXRhWywgaWNvbF0pICAqICg2MCo2MCoyNCksIG9yaWdpbj0iMTg5OS0xMi0zMCIpICMjIyAgbWljcm9zb2Z0IHRpbWVzDQp9DQoNCiMjIGNyZWF0ZSBkYXlvZmFkZGl0aW9uLCBkYXlzb25ob3BzLCBob3BzX2dfMTAwbUwsIHBvdW5kc19iYmwgdmFyaWFibGVzIHdpdGggZHBseXIgIm11dGF0ZSINCm15ZGF0YTwtIG15ZGF0YSAlPiUgDQogIG11dGF0ZShkYXlvZmFkZGl0aW9uID0gYXMuaW50ZWdlcihhcy5udW1lcmljKGRpZmZ0aW1lKGRyeWhvcF9kYXRlLlBPU0lYY3QsYnJld19kYXRlLlBPU0lYY3QpKSksDQogICAgICAgICBkYXlzb25ob3BzID0gYXMuaW50ZWdlcihhcy5udW1lcmljKGRpZmZ0aW1lKFRlc3RfRGF0ZS5QT1NJWGN0LGRyeWhvcF9kYXRlLlBPU0lYY3QpKS8oNjAqNjAqMjQpKSwNCiAgICAgICAgIGhvcHNfZ18xMDBtTCA9IChtZ19ob3BzL3ZvbHVtZV9tTCkvMTAsDQogICAgICAgICBwb3VuZHNfYmJsID0gKG1nX2hvcHMvdm9sdW1lX21MKSoxMTcvNDU0DQogICAgICAgICApDQogICAgICAgICANCm15ZGF0YSRPWDwtZ3JlcGwoIk9YIiwgbXlkYXRhJEhvcF90eXBlKSAgICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgIk9YIiBjb2x1bW4NCm15ZGF0YSRyb3VzZTwtZ3JlcGwoInJvdXNlIiwgbXlkYXRhJHNwZWNpYWxfZ3JvdXApIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJEdyaW5kPC1ncmVwbCgiR3JpbmQiLCBteWRhdGEkSG9wX3R5cGUpICAgICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkQ29uZTwtZ3JlcGwoIkNvbmUiLCBteWRhdGEkSG9wX3R5cGUpICAgICAgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNDwtZ3JlcGwoIjE0IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE1PC1ncmVwbCgiMTUiLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTc8LWdyZXBsKCIxNyIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCg0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgaGFydmVzdHllYXIgKGlmIG5laXRoZXIgMjAxNCBub3IgMjAxNSBub3IgMjAxNywgdGhlbiAyMDE2KQ0KbXlkYXRhJGhhcnZlc3RZZWFyIDwtIGlmZWxzZSgNCiAgbXlkYXRhJGhhcnZlc3QyMDE0PT1UUlVFLCAyMDE0LA0KICBpZmVsc2UobXlkYXRhJGhhcnZlc3QyMDE1PT1UUlVFLCAyMDE1LCANCiAgICAgICAgIGlmZWxzZShteWRhdGEkaGFydmVzdDIwMTc9PVRSVUUsIDIwMTcsIDIwMTYpKSkNCg0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgZm9ybSBvZiBob3BzIChpZiBuZWl0aGVyIGNvbmUgbm9yIGdyb3VuZCBub3IgTkgsIHRoZW4gcGVsbGV0KSANCm15ZGF0YSRmb3JtX29mX2hvcHMgPC0gaWZlbHNlKA0KICBteWRhdGEkQ29uZT09VFJVRSwgImNvbmUiLA0KICBpZmVsc2UobXlkYXRhJEdyaW5kPT1UUlVFLCAiZ3JvdW5kIiwNCiAgICAgICAgIGlmZWxzZShteWRhdGEkSG9wX3R5cGU9PSJOSCIsICJOSCIsICJwZWxsZXQiKSkpDQoNCiMjIGlmZWxzZSBzdGF0ZW1lbnQgZm9yIHRlbXBlcmF0dXJlIGdyZWF0ZXIgb3IgbGVzcyB0aGFuIDEwIA0KbXlkYXRhJERIX3RlbXAgPC0gaWZlbHNlKA0KICBteWRhdGEkdGVtcC5DPDEwLCAiY29sZCIsDQogIGlmZWxzZShteWRhdGEkdGVtcC5DPjEwLCAid2FybSIsICJzb21ldGhpbmcgZWxzZSIpKQ0KDQpteWRhdGEkdmFyaWV0eTwtbXlkYXRhJEhvcF90eXBlDQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIlswLTldKyIsIiIsIG15ZGF0YSR2YXJpZXR5KSAjIyByZW1vdmUgYWxsIG51bWJlcnMNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiT1giLCIiLCBteWRhdGEkdmFyaWV0eSkgICAgICMjIHJlbW92ZSBzcGVjaWZpYyB0ZXh0DQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIkdyaW5kIiwiIiwgbXlkYXRhJHZhcmlldHkpDQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIkNvbmUiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiICIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgICMjIHJlbW92ZSBzcGFjZXMNCg0KbXlkYXRhPC0gbXlkYXRhICU+JSBtdXRhdGUoRVhQVG5ldz1wYXN0ZTAoImdyb3VwIixleHB0LCBzdWJzdHIoc3BlY2lhbF9ncm91cCwgMSwyKSxhcy5jaGFyYWN0ZXIocm91c2UpKSkNCiMgY2xlYW4gaXQgdXAgYnkgcmVtb3ZpbmcgIk5BIiBhbmQgYW55IHNwYWNlcyBkdWUgdG8gY2FuYXJ5Y29kZSBidWcNCm15ZGF0YSRFWFBUbmV3IDwtIGdzdWIoIiAiLCIiLCBteWRhdGEkRVhQVG5ldykgICMjIHJlbW92ZSBhbnkgc3BhY2VzDQpteWRhdGEkRVhQVG5ldyA8LSBnc3ViKCJOQSIsIiIsIG15ZGF0YSRFWFBUbmV3KSAjIyByZW1vdmUgIk5BIg0KDQojIyBzZWxlY3QgYW5kIHJlYXJyYW5nZSBjb2x1bW5zIA0KbXlkYXRhIDwtIG15ZGF0YSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2FtcGxlX2lkLGV4cHQsIEVYUFRuZXcsIGhvcCwgQklOaG9wLCB2YXJpZXR5LCBPWCwgaGFydmVzdFllYXIsIGZvcm1fb2ZfaG9wcyxzcGVjaWFsX2NvbmRpdGlvbnMsIHJvdXNlLCBkYXlzb25ob3BzLCBkYXlvZmFkZGl0aW9uLCBESF90ZW1wLCB0ZW1wLkMsIGhvcHNfZ18xMDBtTCwgcG91bmRzX2JibCwNCiAgICAgICAgIEFCViwgQUJXLCBPRSwgRXIsIEVhLCBTRywgUkRGLCBBREYsIENhbG9yaWVzLCANCiAgICAgICAgIGRob3BfZGF5LCBjb250YWN0X2RheXMsIFJFRl9OSCwgQUJWX2luY3JlYXNlLCANCiAgICAgICAgIGJyZXdfZGF0ZS5QT1NJWGN0LCBzYW1wbGVfY29sbGVjdGlvbl9kYXRlLlBPU0lYY3QsIGRyeWhvcF9kYXRlLlBPU0lYY3QsVGVzdF9EYXRlLlBPU0lYY3QsIGluaXRpYWxfcGxhdG8pDQojIyByZW1vdmUgIi5QT1NJWGN0IiBzdWZmaXguICBMZWF2aW5nIGl0IGFzLWlzIHdpbGwgb25seSBhZGQgdG8gY29uZnVzaW9uIGlmL3doZW4gdGhlc2UgZGF0YSBhcmUgc2F2ZWQgYW5kIHJlLWltcG9ydGVkIChhbmQgYmVjb21lICdjaGFyYWN0ZXInIGZvcm1hdCEpDQpjb2xuYW1lcyhteWRhdGEpID0gZ3N1YigiLlBPU0lYY3QiLCAiIiwgY29sbmFtZXMobXlkYXRhKSkNCiMjY29tcHV0ZSBtZWFuIE5IIChjb250cm9sKSB2YWx1ZXMgDQojIyBtZWFuIE5IIChjb250cm9sKSB2YWx1ZXMgZm9yIGVhY2ggRVhQVG5ldyBncm91cA0KbWVhbi5jb250cm9sX05IPC0gbXlkYXRhICU+JSANCiAgZ3JvdXBfYnkoRVhQVG5ldykgJT4lDQogIGZpbHRlcihob3A9PSJOSCIpICU+JQ0KICBzdW1tYXJpc2VfYXQodmFycyhBQlYsIEFCVywgRWEsIFNHKSxmdW5zKG1lYW4sIG4oKSkpDQojIyByZXBsYWNlICJfbWVhbiIgd2l0aCAiLmNvbnRyb2xfTkgiIGluIGNvbHVtbiBuYW1lcw0KY29sbmFtZXMobWVhbi5jb250cm9sX05IKSA9IGdzdWIoIl9tZWFuIiwgIi5jb250cm9sX05IIiwgY29sbmFtZXMobWVhbi5jb250cm9sX05IKSkNCiMjY29tcHV0ZSAkXERlbHRhJCB2YWx1ZXMgKGNoYW5nZXMgcmVsYXRpdmUgdG8gdW4tZHJ5aG9wcGVkIGNvbnRyb2xzKSB1c2luZyBvYmplY3RzIGNyZWF0ZWQgYWJvdmUuLi4NCiMjIGZpcnN0IGpvaW4gb3VyIGRhdGEgd2l0aCBtZWFuIEFCViBmb3IgdW5ob3BwZWQgc2FtcGxlcyBpbiBnaXZlbiBleHBlcmltZW50IChtZWFuLmNvbnRyb2xfTkg7IGNhbGN1bGF0ZWQgYWJvdmUpDQoNCkZQSGNhbGM8LSBsZWZ0X2pvaW4obXlkYXRhLCBtZWFuLmNvbnRyb2xfTkgsIGJ5PSJFWFBUbmV3IikNCg0KIyMgY2FsY3VsYXRlIEFCV19pbmNyZWFzZSBieSBzdWJ0cmFjdGluZyBlYWNoIGluZGl2aWR1YWwgQUJXIG1lYXN1cmVtZW50IGZyb20gcmVzcGVjdGl2ZSBtZWFuLmNvbnRyb2xfTkg6DQpGUEhjYWxjJGRlbHRhLkFCViA8LSBGUEhjYWxjJEFCViAtIEZQSGNhbGMkQUJWLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEuQUJXIDwtIEZQSGNhbGMkQUJXIC0gRlBIY2FsYyRBQlcuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5wbGF0byA8LSBGUEhjYWxjJEVhIC0gRlBIY2FsYyRFYS5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLlNHIDwtIEZQSGNhbGMkU0cgLSBGUEhjYWxjJFNHLmNvbnRyb2xfTkgNCg0KIyMgdGhlIGNvbnRyb2wgc2FtcGxlcyBoYXZlIHNlcnZlZCB0aGVpciBwdXJwb3NlLCBub3cgcmVtb3ZlIHRoZW0gZnJvbSBkYXRhc2V0LiBUaGUgZm9sbG93aW5nIGNhbGN1bGF0aW9ucyBhcmUgb25seSBtZWFuaW5nZnVsIGZvciBkcnktaG9wcGVkIHNhbXBsZXMuICANCkZQSGNhbGM8LSBGUEhjYWxjICU+JSBmaWx0ZXIoaG9wPT0iREgiKQ0KIyMgKmNhbGNhbGF0ZSogY29ycmVzcG9uZGluZyBDTzIgcHJvZHVjdGlvbiANCkZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSA8LSBGUEhjYWxjJGRlbHRhLkFCVyooMC40NC8wLjQ2KQ0KIyNjb252ZXJ0IGNhbGNDTzJfaW5jcmVhc2UgKGluIGcvMTAwbUwpIHRvIGNhbGN1bGF0ZWQgQ08yIHZvbHVtZXMgYWRkZWQNCiMjIGcvTCA9IDEwKiBnLzEwMG1MDQojIyBUaGUgY29udmVyc2lvbiBmYWN0b3IgZnJvbSB2b2x1bWVzIG9mIENPMiB0byBDTzIgYnkgd2VpZ2h0IChnL0wpIGlzIDEuOTYuIEZvciBleGFtcGxlOiAyLjUgdm9sdW1lcyB4IDEuOTYgPSA0LjkgZy9sLg0KRlBIY2FsYyRjYWxjQ08ydm9sc19pbmNyZWFzZSA8LSBGUEhjYWxjJGNhbGNDTzJfaW5jcmVhc2UqMTAvMS45Ng0KIyMgZGVmaW5lICJGUEgiIGFzIGFtb3VudCBwcm9kdWNlZCBwZXIgJSBkcnktaG9wcyBhZGRlZCAoaW4gZy8xMDBtTCk6DQojIyAgRlBIID0gRm9sZCBQcm9kdWN0aW9uIGR1ZSB0byBIb3BzIChmb2xkLWluY3JlYXNlIGJ5IG1hc3M6IGFtb3VudCBvZiBnaXZlbiBlbmRwb2ludCByZWxhdGl2ZSB0byBhbW91bnQgb2YgaG9wcyBhZGRlZCkNCiMjICANCkZQSGNhbGMkRlBIX0V0T0ggPSBGUEhjYWxjJGRlbHRhLkFCVy9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KRlBIY2FsYyRGUEhfQ08yID0gRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQpGUEhjYWxjJEZQSF9wbGF0byA9IEZQSGNhbGMkZGVsdGEucGxhdG8vRlBIY2FsYyRob3BzX2dfMTAwbUwNCg0KIyMgcmVwbGFjaW5nIHRpbWUtemVybyB0aW1lIHZhbHVlcyB3aXRoIGEgdmVyeSBzbWFsbCBudW1iZXIgKHJhdGhlciB0aGFuIGV4YWN0bHkgemVybykgd2lsbCBwcmV2ZW50IGlzc3VlcyB3aXRoIGFuYWx5c2lzIG9mIG5vbmxpbmVhciBtb2RlbHMNCkZQSGNhbGNbRlBIY2FsYyRkYXlzb25ob3BzPT0wLF0kZGF5c29uaG9wcyA8LSAwLjAxDQoNCiMjIGFuZCBzYXZlIHRoZSB0cmFuc2Zvcm1lZCBkYXRhIHRvIGNzdjoNCndyaXRlLmNzdihGUEhjYWxjLCJGUEhjYWxjLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KRlBIY2FsYyA8LSByZWFkLmNzdigiRlBIY2FsYy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNCg0KZGY8LSBGUEhjYWxjICU+JSBncm91cF9ieShFWFBUbmV3KSAlPiUNCiAgc3VtbWFyaXNlX2F0KHZhcnMoaG9wc19nXzEwMG1MLHBvdW5kc19iYmwsZGVsdGEuQUJWLCBkZWx0YS5BQlcsIGRlbHRhLnBsYXRvLCBGUEhfcGxhdG8sIEZQSF9FdE9ILCBGUEhfQ08yLCBjYWxjQ08ydm9sc19pbmNyZWFzZSksZnVucyhyb3VuZChtZWFuKC4pLCAyKSkpICU+JQ0KICBhcnJhbmdlKGRlc2MoRlBIX0V0T0gpKSANCmRmDQpgYGANCg0KDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=